mirror of https://github.com/lukechilds/docs.git
Moxiegirl
6 years ago
committed by
GitHub
1 changed files with 0 additions and 839 deletions
@ -1,839 +0,0 @@ |
|||
--- |
|||
layout: learn |
|||
permalink: /:collection/:path.html |
|||
--- |
|||
|
|||
# Manage Data with Gaia |
|||
|
|||
{:.no_toc} |
|||
|
|||
In this tutorial, you build a micro-blogging application using multi-player Gaia |
|||
storage. Gaia is Blockstack's |
|||
[decentralized high-performance storage system](https://github.com/blockstack/gaia). |
|||
The tutorial contains the following topics: |
|||
|
|||
- TOC {:toc} |
|||
|
|||
This tutorial does not teach you about authentication. That is covered in depth |
|||
[in the hello-blockstack tutorial](hello-blockstack.html). |
|||
|
|||
<!--TODO: authentication tutorial--> |
|||
<!--Strictly speaking not sure it is necessary here to send them out--> |
|||
|
|||
## About this tutorial and the prerequisites you need |
|||
|
|||
{% include note.html content="This tutorial was written for a user running macOS. If you are running a Windows |
|||
machine, you can still work through this tutorial. You will need to adjust the |
|||
instructions for your environment."%} |
|||
|
|||
The application you build is a React.js application that is completely |
|||
decentralized and server-less. While not strictly required to follow along, |
|||
basic familiarity with React.js is helpful. |
|||
|
|||
When complete, the app is capable of the following: |
|||
|
|||
- authenticating users using Blockstack |
|||
- posting new statuses |
|||
- displaying statuses in the user profile |
|||
- looking up the profiles and statuses of other users |
|||
|
|||
The basic identity and storage services are provided by `blockstack.js`. To test |
|||
the application, you need to have already |
|||
[registered a Blockstack ID](ids-introduction.html). |
|||
|
|||
The tutorial relies on the `npm` dependency manager. Before you begin, verify |
|||
you have installed `npm` using the `which` command. |
|||
|
|||
```bash |
|||
$ which npm |
|||
/usr/local/bin/npm |
|||
``` |
|||
|
|||
If you don't find `npm` in your system, |
|||
[install it](https://www.npmjs.com/get-npm). Finally, if you get stuck at any |
|||
point while working on the tutorial, the completed |
|||
[source code is available for you](https://github.com/larrysalibra/publik) to |
|||
check your work against. |
|||
|
|||
High Sierra 10.13.4. |
|||
|
|||
## Use npm to install Yeoman and the Blockstack App Generator |
|||
|
|||
You use `npm` to install Yeoman. Yeoman is a generic scaffolding system that |
|||
helps users rapidly start new projects and streamline the maintenance of |
|||
existing projects. |
|||
|
|||
1. Install Yeoman. |
|||
|
|||
```bash |
|||
npm install -g yo |
|||
``` |
|||
|
|||
2. Install the Blockstack application generator. |
|||
|
|||
```bash |
|||
npm install -g generator-blockstack |
|||
``` |
|||
|
|||
<!-- Need to find out if user is required to have React installed before running Yeoman. Doesn't appear to be the case. --> |
|||
|
|||
## Generate and launch the public application |
|||
|
|||
In this section, you build an initial React.js application called Publik. |
|||
|
|||
1. Create a the `publik` directory. |
|||
|
|||
```bash |
|||
mkdir publik |
|||
``` |
|||
|
|||
2. Change into your new directory. |
|||
|
|||
```bash |
|||
cd publik |
|||
``` |
|||
|
|||
3. Use Yeoman and the Blockstack application generator to create your initial |
|||
`publik` application. |
|||
|
|||
```bash |
|||
yo blockstack:react |
|||
``` |
|||
|
|||
You should see several interactive prompts. |
|||
|
|||
```bash |
|||
$ yo blockstack:react |
|||
? ========================================================================== |
|||
We're constantly looking for ways to make yo better! |
|||
May we anonymously report usage statistics to improve the tool over time? |
|||
More info: https://github.com/yeoman/insight & http://yeoman.io |
|||
========================================================================== No |
|||
|
|||
_-----_ ╭──────────────────────────╮ |
|||
| | │ Welcome to the │ |
|||
|--(o)--| │ Blockstack app │ |
|||
`---------´ │ generator! │ |
|||
( _´U`_ ) ╰──────────────────────────╯ |
|||
/___A___\ / |
|||
| ~ | |
|||
__'.___.'__ |
|||
´ ` |° ´ Y ` |
|||
|
|||
? Are you ready to build a Blockstack app in React? (Y/n) |
|||
``` |
|||
|
|||
4. Respond to the prompts to populate the initial app. |
|||
|
|||
After the process completes successfully, you see a prompt similar to the |
|||
following: |
|||
|
|||
```bash |
|||
[fsevents] Success: |
|||
"/Users/theuser/repos/publik/node_modules/fsevents/lib/binding/Release/node-v59-darwin-x64/fse.node" |
|||
is installed via remote npm notice created a lockfile as package-lock.json. |
|||
You should commit this file. added 1060 packages in 26.901s |
|||
``` |
|||
|
|||
5. Run the initial application. |
|||
|
|||
```bash |
|||
npm start |
|||
``` |
|||
|
|||
The system prompts you to accept incoming connections. |
|||
|
|||
![Network Connection](./images/network-connections.gif) |
|||
|
|||
6. Choose **Allow**. |
|||
|
|||
7. Open your browser to `http://localhost:8080`. |
|||
|
|||
You should see a simple React app. |
|||
|
|||
![](images/initial-app.gif) |
|||
|
|||
8. Choose **Sign In with Blockstack**. |
|||
|
|||
The application tells you it will **Read your basic info**. |
|||
|
|||
![](images/login.png) |
|||
|
|||
Leave your new application running and move onto the next section. |
|||
|
|||
## Add the `publish_data` scope to sign in requests |
|||
|
|||
Every app that uses Gaia storage must add itself to the user's `profile.json` |
|||
file. The Blockstack browser does this automatically when the `publish_data` |
|||
scope is requested during authentication. For this application, the user files |
|||
stored on Gaia are made visible to others via the `apps` property in the user's |
|||
`profile.json` file. |
|||
|
|||
Modify your authentication request to include the `publish_data` scope. |
|||
|
|||
1. Open `src/components/App.jsx` file. |
|||
|
|||
2. Locate the `handleSignIn` handler method. |
|||
|
|||
```javascript |
|||
handleSignIn(e) { |
|||
e.preventDefault(); |
|||
redirectToSignIn(); |
|||
} |
|||
``` |
|||
|
|||
3. Modify the method to this: |
|||
|
|||
```javascript |
|||
handleSignIn(e) { |
|||
e.preventDefault(); |
|||
const origin = window.location.origin |
|||
redirectToSignIn(origin, origin + '/manifest.json', ['store_write', 'publish_data']) |
|||
} |
|||
``` |
|||
|
|||
By default, authentication requests include the `store_write` scope which |
|||
enables storage. This is what allows you to store information to Gaia. |
|||
|
|||
4. Save your changes. |
|||
5. Go back to your app at `http://localhost:8080/`. |
|||
6. Log out and sign in again. |
|||
|
|||
The authentication request now prompts the user for permission to **Publish |
|||
data stored for the app**. |
|||
|
|||
![](images/publish-data-perm.png) |
|||
|
|||
## Understand Gaia storage methods |
|||
|
|||
Once you authenticate a user with `store_write` and `publish_data`, you can |
|||
begin to manage data for your users. Blockstack JS provides two methods |
|||
`getFile()` and `putFile()` for interacting with Gaia storage. The storage |
|||
methods support all file types. This means you can store SQL, Markdown, JSON, or |
|||
even a custom format. |
|||
|
|||
You can create a meaningful and complex data layer using these two methods. |
|||
Before creating an application, consider fundamental data architecture and make |
|||
some decisions about how you’re modeling data. For example, consider building a |
|||
simple grocery list app. A user should be able to create, read, update, and |
|||
delete grocery lists. |
|||
|
|||
A single file collection stores items as an array nested inside each grocery |
|||
list: |
|||
|
|||
```js |
|||
// grocerylists.json |
|||
{ |
|||
"3255": { |
|||
"items": [ |
|||
"1 Head of Lettuce", |
|||
"Haralson apples" |
|||
] |
|||
}, |
|||
// ...more lists with items |
|||
} |
|||
``` |
|||
|
|||
This is conceptually the simplest way to manage grocery lists. When you read a |
|||
`/grocerylists.json` file with `getFile()`, you get back one or more grocery |
|||
lists and their items. When you write a single list, the `putFile()` method |
|||
overwrites the entire list. So, a write operation for a new or updated grocery |
|||
list must submit all existings lists as well. |
|||
|
|||
Further, because this runs on the client where anything can go wrong. If the |
|||
client-side code encounters a parsing error with a user-input value and you |
|||
could overwrite the entire file with: |
|||
|
|||
`line 6: Parsing Error: Unexpected token.` |
|||
|
|||
Further, a single file makes pagination impossible and if your app stores a |
|||
single file for all list you have less control over file permissions. To avoid |
|||
these issues, you can create an index file that stores an array of IDs. These |
|||
IDs point to a name of another file in a `grocerylists` folder. |
|||
|
|||
![](images/multiple-lists.png) |
|||
|
|||
This design allows you to get only the files you need and avoid accidentally |
|||
overwriting all lists. Further, you’re only updating the index file when you add |
|||
or remove a grocery list; updating a list has no impact. |
|||
|
|||
## Add support for user status submission and lookup |
|||
|
|||
In this step, you add three `blockstack.js` methods that support posting of |
|||
"statuses". These are the `putFile()`, `getFile()`, and `lookupProfile()` |
|||
methods. |
|||
|
|||
1. Open the `src/components/Profile.jsx` file. |
|||
|
|||
2. Expand the `import from blockstack` statement with data methods. |
|||
|
|||
The `Person` object holds a Blockstack profile. Add `putFile`, `getFile`, |
|||
and `lookupProfile` after `Person`. |
|||
|
|||
When you are done, the import statement should look like the following: |
|||
|
|||
```javascript |
|||
import { |
|||
isSignInPending, |
|||
loadUserData, |
|||
Person, |
|||
getFile, |
|||
putFile, |
|||
lookupProfile |
|||
} from 'blockstack'; |
|||
``` |
|||
|
|||
3. Replace the `constructor()` initial state so that it holds the key |
|||
properties required by the app. |
|||
|
|||
This code constructs a Blockstack `Person` object to hold the profile. Your |
|||
constructor should look like this: |
|||
|
|||
```javascript |
|||
constructor(props) { |
|||
super(props); |
|||
|
|||
this.state = { |
|||
person: { |
|||
name() { |
|||
return 'Anonymous'; |
|||
}, |
|||
avatarUrl() { |
|||
return avatarFallbackImage; |
|||
}, |
|||
}, |
|||
username: "", |
|||
newStatus: "", |
|||
statuses: [], |
|||
statusIndex: 0, |
|||
isLoading: false |
|||
}; |
|||
} |
|||
``` |
|||
|
|||
4) Locate the `render()` method. |
|||
5) Modify the `render()` method to add a text input and submit button to the |
|||
application. |
|||
|
|||
The following code echos the `person.name` and `person.avatarURL` properties |
|||
from the profile on the display: |
|||
|
|||
```javascript |
|||
render() { |
|||
const { handleSignOut } = this.props; |
|||
const { person } = this.state; |
|||
const { username } = this.state; |
|||
|
|||
return ( |
|||
!isSignInPending() && person ? |
|||
<div className="container"> |
|||
<div className="row"> |
|||
<div className="col-md-offset-3 col-md-6"> |
|||
<div className="col-md-12"> |
|||
<div className="avatar-section"> |
|||
<img |
|||
src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage } |
|||
className="img-rounded avatar" |
|||
id="avatar-image" |
|||
/> |
|||
<div className="username"> |
|||
<h1> |
|||
<span id="heading-name">{ person.name() ? person.name() |
|||
: 'Nameless Person' }</span> |
|||
</h1> |
|||
<span>{username}</span> |
|||
<span> |
|||
| |
|||
<a onClick={ handleSignOut.bind(this) }>(Logout)</a> |
|||
</span> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div className="new-status"> |
|||
<div className="col-md-12"> |
|||
<textarea className="input-status" |
|||
value={this.state.newStatus} |
|||
onChange={e => this.handleNewStatusChange(e)} |
|||
placeholder="Enter a status" |
|||
/> |
|||
</div> |
|||
<div className="col-md-12"> |
|||
<button |
|||
className="btn btn-primary btn-lg" |
|||
onClick={e => this.handleNewStatusSubmit(e)} |
|||
> |
|||
Submit |
|||
</button> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</div> |
|||
</div> : null |
|||
); |
|||
} |
|||
``` |
|||
|
|||
This code allows the application to post statuses. It also displays the |
|||
user's Blockstack ID. To display this, your app must extract the ID from the |
|||
user profile data. |
|||
|
|||
6) Locate the `componentWillMount()` method. |
|||
7) Add the `username` property below the `person` property. |
|||
|
|||
You'll use the Blockstack `loadUserData()` method to access the `username`. |
|||
|
|||
|
|||
```javascript |
|||
componentWillMount() { |
|||
this.setState({ |
|||
person: new Person(loadUserData().profile), |
|||
username: loadUserData().username |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
7. Add two methods to handle the status input events: |
|||
|
|||
```javascript |
|||
handleNewStatusChange(event) { |
|||
this.setState({newStatus: event.target.value}) |
|||
} |
|||
|
|||
handleNewStatusSubmit(event) { |
|||
this.saveNewStatus(this.state.newStatus) |
|||
this.setState({ |
|||
newStatus: "" |
|||
}) |
|||
} |
|||
``` |
|||
|
|||
8. Add a `saveNewStatus()` method to save the new statuses. |
|||
|
|||
```javascript |
|||
saveNewStatus(statusText) { |
|||
let statuses = this.state.statuses |
|||
|
|||
let status = { |
|||
id: this.state.statusIndex++, |
|||
text: statusText.trim(), |
|||
created_at: Date.now() |
|||
} |
|||
|
|||
statuses.unshift(status) |
|||
const options = { encrypt: false } |
|||
putFile('statuses.json', JSON.stringify(statuses), options) |
|||
.then(() => { |
|||
this.setState({ |
|||
statuses: statuses |
|||
}) |
|||
}) |
|||
} |
|||
``` |
|||
|
|||
9. Save the `Profile.jsk` file. |
|||
|
|||
After the application compiles successfully, your application should appears |
|||
as follows: |
|||
|
|||
![](images/display-complete.png) |
|||
|
|||
10. Enter your status in the text box and press the **Submit** button. |
|||
|
|||
At this point, nothing is blogged. In the next section you add code to |
|||
display the statuses back to the user as a blog entry. |
|||
|
|||
## Fetch and display statuses |
|||
|
|||
Update `Profile.jsx` again. |
|||
|
|||
1. Go back to the `render()` method. |
|||
2. Locate the `<div className="new-status">` containing the text input and |
|||
**Submit** button. |
|||
3. Right after this opening `div` element, add this block. |
|||
|
|||
```javascript |
|||
<div className='col-md-12 statuses'> |
|||
{this.state.isLoading && <span>Loading...</span>} |
|||
{this.state.statuses.map(status => ( |
|||
<div className='status' key={status.id}> |
|||
{status.text} |
|||
</div> |
|||
))} |
|||
</div> |
|||
``` |
|||
|
|||
This loads existing state. Your code needs to fetch statuses on page load. |
|||
|
|||
4. Add a new method called `fetchData()` after the `statuses.unshift(status)` |
|||
section. |
|||
|
|||
```javascript |
|||
|
|||
fetchData() { |
|||
this.setState({ isLoading: true }) |
|||
const options = { decrypt: false } |
|||
getFile('statuses.json', options) |
|||
.then((file) => { |
|||
var statuses = JSON.parse(file || '[]') |
|||
this.setState({ |
|||
person: new Person(loadUserData().profile), |
|||
username: loadUserData().username, |
|||
statusIndex: statuses.length, |
|||
statuses: statuses, |
|||
}) |
|||
}) |
|||
.finally(() => { |
|||
this.setState({ isLoading: false }) |
|||
}) |
|||
} |
|||
``` |
|||
|
|||
5. Call `fetchData()` from the `componentDidMount()` method |
|||
|
|||
```javascript |
|||
|
|||
componentDidMount() { |
|||
this.fetchData() |
|||
} |
|||
``` |
|||
|
|||
6. Save the file. |
|||
|
|||
After the application compiles successfully, users are able to **Submit** |
|||
multiple statuses and review them in the app. |
|||
|
|||
![](images/saving-status.png) |
|||
|
|||
## Change the style |
|||
|
|||
1. Edit the `src/styles/style.css` file. |
|||
2. Replace the content with the following: |
|||
|
|||
|
|||
```css |
|||
/* Globals */ |
|||
a,a:focus,a:hover{color:#fff;} |
|||
html,body{height:100%;text-align:center;background-color:#191b22;} |
|||
body{color:#fff} |
|||
.hide{display:none;} |
|||
.landing-heading{font-family:'Lato',Sans-Serif;font-weight:400;} |
|||
|
|||
/* Buttons */ |
|||
.btn{font-family:'Lato',Sans-Serif;padding:0.5625rem 2.5rem;font-size:0.8125rem;font-weight:400;line-height:1.75rem;border-radius:0!important;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;} |
|||
.btn-lg{font-size:1.5rem;padding:0.6875rem 3.4375rem;line-height:2.5rem;} |
|||
.btn:focus,.btn:active:focus,.btn.active:focus{outline:none;} |
|||
.btn-primary{color:#fff;border:1px solid #2C96FF;background-color:#2C96FF;} |
|||
.btn-primary:hover,.btn-primary:focus,.btn-primary:active{color:#fff;border:1px solid #1a6ec0;background-color:#1a6ec0;} |
|||
|
|||
/* Avatar */ |
|||
.avatar{width:100px;height:100px;} |
|||
.avatar-section{margin-bottom:25px;display:flex;text-align:left;} |
|||
.username{margin-left:20px;} |
|||
|
|||
/* Scaffolding */ |
|||
.site-wrapper{display:table;width:100%;height:100vh;min-height:100%;} |
|||
.site-wrapper-inner{display:flex;flex-direction:column;justify-content:center;margin-right:auto;margin-left:auto;width:100%;height:100vh;} |
|||
.panel-authed{padding:0 0 0 0;} |
|||
|
|||
/* Home button */ |
|||
.btn-home-hello{position:absolute;font-family:'Source Code Pro',monospace;font-size:11px;font-weight:400;color:rgba(255,255,255,0.85);top:15px;left:15px;padding:3px 20px;background-color:rgba(255,255,255,0.15);border-radius:6px;-webkit-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);-moz-box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);box-shadow:0px 0px 20px 0px rgba(0,0,0,0.15);} |
|||
|
|||
/* Input */ |
|||
input, textarea{color:#000;padding:10px;} |
|||
.input-status{width:100%;height:70px;border-radius:6px;} |
|||
.new-status{text-align:right;} |
|||
|
|||
/* Statuses */ |
|||
.statuses{padding-top:30px;} |
|||
.status{margin:15px 0px;padding:20px;background-color:#2e2e2e;border-radius:6px} |
|||
``` |
|||
|
|||
3. Save and close the `src/styles/style.css` file. |
|||
|
|||
After the application compiles, you should see the following: |
|||
|
|||
![Multi-reader storage authentication](images/multi-player-storage-status.png) |
|||
|
|||
At this point, you have a basic micro-blogging app that users can use to post |
|||
and view statuses. However, there's no way to view other users' statuses. You'll |
|||
add that in the next section. |
|||
|
|||
## Lookup user profiles |
|||
|
|||
Let's now modify the `Profile.jsx` file to display profiles of other users. |
|||
You'll be using the `lookupProfile()` method that you added to the `import` |
|||
statement earlier. `lookupProfile()` takes a single parameter that is the |
|||
Blockstack ID of the profile and returns a profile object. |
|||
|
|||
### Add a new route |
|||
|
|||
Make some changes to the routing structure of your app so that users can view |
|||
other users' profiles by visiting `http://localhost:8080/other_user.id` |
|||
|
|||
1. Make sure you are in the root of your `publik` project. |
|||
2. Install `react-router`: |
|||
|
|||
```bash |
|||
npm install --save react-router-dom |
|||
``` |
|||
|
|||
3. Edit `src/index.js` file. |
|||
4. Add an `import` to the file at the top: |
|||
|
|||
```javascript |
|||
import {BrowserRouter} from 'react-router-dom'; |
|||
``` |
|||
|
|||
5. Change the `ReactDOM.render()` method in `src/index.js` to: |
|||
|
|||
```javascript |
|||
ReactDOM.render( |
|||
<BrowserRouter> |
|||
<App /> |
|||
</BrowserRouter>, |
|||
document.getElementById('root') |
|||
); |
|||
``` |
|||
|
|||
6. Save and close the `src/index.js` file. |
|||
7. Edit the `src/components/App.jsx` file. |
|||
8. Add the new route by importing the `Switch` and `Route` components from |
|||
`react-router-dom`: |
|||
|
|||
```javascript |
|||
import {Switch, Route} from 'react-router-dom'; |
|||
``` |
|||
|
|||
9. Locate this line below in the `render()` method: |
|||
|
|||
```javascript |
|||
: <Profile handleSignOut={ this.handleSignOut } /> |
|||
``` |
|||
|
|||
10. Replace it with the following: |
|||
|
|||
```javascript |
|||
: |
|||
<Switch> |
|||
<Route |
|||
path='/:username?' |
|||
render={ |
|||
routeProps => <Profile handleSignOut={ this.handleSignOut } {...routeProps} /> |
|||
} |
|||
/> |
|||
</Switch> |
|||
``` |
|||
|
|||
This sets up a route and captures the route parameter the app will use as |
|||
the profile lookup username. |
|||
|
|||
11. Save and close the `src/components/App.jsx` file. |
|||
|
|||
### Add a rule to process URL paths with . (dot) |
|||
|
|||
You also need to add a rule to your webpack config so that you can properly |
|||
process URL paths that contain the `.` (dot) character for example, |
|||
`http://localhost:8080/other_user.id` |
|||
|
|||
**NOTE**: In a production app, you must ensure the web server is configured to |
|||
handle this. |
|||
|
|||
1. Open `webpack.config.js` in the root project directory and locate the |
|||
following line: |
|||
|
|||
```javascript |
|||
historyApiFallback: true, |
|||
``` |
|||
|
|||
2. Replace it with this: |
|||
|
|||
```javascript |
|||
historyApiFallback: { |
|||
disableDotRule: true |
|||
}, |
|||
``` |
|||
|
|||
You will need to run `npm start` again for this change to take effect. Don't |
|||
worry, there is a later step for that to remind you. |
|||
|
|||
3. Save and close the `webpack.config.js` file. |
|||
|
|||
4. Edit the `src/components/Profile.jsx` file. |
|||
5. Add a single method that determines if the app is viewing the local user's |
|||
profile or another user's profile. |
|||
|
|||
```javascript |
|||
isLocal() { |
|||
return this.props.match.params.username ? false : true |
|||
} |
|||
``` |
|||
|
|||
You use `isLocal()` to check if the user is viewing the local user profile |
|||
or another user's profile. If it's the local user profile, the app runs the |
|||
`getFile()` function you added in an earlier step. Otherwise, the app looks |
|||
up the profile belonging to the `username` using the `lookupProfile()` |
|||
method. |
|||
|
|||
6. Modify the `fetchData()` method like so: |
|||
|
|||
```javascript |
|||
fetchData() { |
|||
this.setState({ isLoading: true }) |
|||
if (this.isLocal()) { |
|||
const options = { decrypt: false } |
|||
getFile('statuses.json', options) |
|||
.then((file) => { |
|||
var statuses = JSON.parse(file || '[]') |
|||
this.setState({ |
|||
person: new Person(loadUserData().profile), |
|||
username: loadUserData().username, |
|||
statusIndex: statuses.length, |
|||
statuses: statuses, |
|||
}) |
|||
}) |
|||
.finally(() => { |
|||
this.setState({ isLoading: false }) |
|||
}) |
|||
} else { |
|||
const username = this.props.match.params.username |
|||
|
|||
lookupProfile(username) |
|||
.then((profile) => { |
|||
this.setState({ |
|||
person: new Person(profile), |
|||
username: username |
|||
}) |
|||
}) |
|||
.catch((error) => { |
|||
console.log('could not resolve profile') |
|||
}) |
|||
} |
|||
} |
|||
``` |
|||
|
|||
**NOTE**: For `https` deployments, the default Blockstack Core API endpoint |
|||
for name lookups should be changed to point to a core API served over |
|||
`https`. Otherwise, name lookups fail due to browsers blocking mixed |
|||
content. Refer to the |
|||
[Blockstack.js documentation](http://blockstack.github.io/blockstack.js/#getfile) |
|||
for details. |
|||
|
|||
7. Add the following block to `fetchData()` right after the call to |
|||
`lookupProfile(username)... catch((error)=>{..}` block: |
|||
|
|||
```javascript |
|||
const options = {username: username, decrypt: false}; |
|||
getFile('statuses.json', options) |
|||
.then(file => { |
|||
var statuses = JSON.parse(file || '[]'); |
|||
this.setState({ |
|||
statusIndex: statuses.length, |
|||
statuses: statuses |
|||
}); |
|||
}) |
|||
.catch(error => { |
|||
console.log('could not fetch statuses'); |
|||
}) |
|||
.finally(() => { |
|||
this.setState({isLoading: false}); |
|||
}); |
|||
``` |
|||
|
|||
This fetches the user statuses. |
|||
|
|||
Finally, you must conditionally render the logout button, status input |
|||
textbox, and submit button so they don't show up when viewing another user's |
|||
profile. |
|||
|
|||
8. Replace the `render()` method with the following: |
|||
|
|||
```javascript |
|||
render() { |
|||
const { handleSignOut } = this.props; |
|||
const { person } = this.state; |
|||
const { username } = this.state; |
|||
|
|||
return ( |
|||
!isSignInPending() && person ? |
|||
<div className="container"> |
|||
<div className="row"> |
|||
<div className="col-md-offset-3 col-md-6"> |
|||
<div className="col-md-12"> |
|||
<div className="avatar-section"> |
|||
<img |
|||
src={ person.avatarUrl() ? person.avatarUrl() : avatarFallbackImage } |
|||
className="img-rounded avatar" |
|||
id="avatar-image" |
|||
/> |
|||
<div className="username"> |
|||
<h1> |
|||
<span id="heading-name">{ person.name() ? person.name() |
|||
: 'Nameless Person' }</span> |
|||
</h1> |
|||
<span>{username}</span> |
|||
{this.isLocal() && |
|||
<span> |
|||
| |
|||
<a onClick={ handleSignOut.bind(this) }>(Logout)</a> |
|||
</span> |
|||
} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
{this.isLocal() && |
|||
<div className="new-status"> |
|||
<div className="col-md-12"> |
|||
<textarea className="input-status" |
|||
value={this.state.newStatus} |
|||
onChange={e => this.handleNewStatusChange(e)} |
|||
placeholder="What's on your mind?" |
|||
/> |
|||
</div> |
|||
<div className="col-md-12 text-right"> |
|||
<button |
|||
className="btn btn-primary btn-lg" |
|||
onClick={e => this.handleNewStatusSubmit(e)} |
|||
> |
|||
Submit |
|||
</button> |
|||
</div> |
|||
</div> |
|||
} |
|||
<div className="col-md-12 statuses"> |
|||
{this.state.isLoading && <span>Loading...</span>} |
|||
{this.state.statuses.map((status) => ( |
|||
<div className="status" key={status.id}> |
|||
{status.text} |
|||
</div> |
|||
) |
|||
)} |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> : null |
|||
); |
|||
} |
|||
``` |
|||
|
|||
### This checks to ensure that users are viewing their own profile, by wrapping the **Logout** button and inputs with the `{isLocal() && ...}` condition. |
|||
|
|||
### Put it all together |
|||
|
|||
1. Stop the running application by sending a CTL-C. |
|||
2. Restart the application so that the disabling of the `.` (dot) rule takes |
|||
effect. |
|||
|
|||
```bash |
|||
npm start |
|||
``` |
|||
|
|||
3. Point your browser to `http://localhost:8080/your_blockstack.id` to see the |
|||
final application. |
|||
|
|||
## Wrapping up |
|||
|
|||
Congratulations, you are all done! We hope you've enjoyed learning a bit more |
|||
about Blockstack. To use a working version of the app go |
|||
[here](http://publik.ykliao.com). |
Loading…
Reference in new issue