Skip to content

Commit bf0a460

Browse files
committed
profile page and redux
1 parent d3f2de9 commit bf0a460

File tree

133 files changed

+38439
-7522
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

133 files changed

+38439
-7522
lines changed

README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@
44
# Journal
55
Step by step tutorial to build a personal journal web app with ReactJS + AWS
66

7-
**Update:** I am updating this tutorial with AWS Amplify 1.1 and Bootstrap 4.1. So far re-worked through step-03. Stay tuned.
7+
**Update:** I am updating this tutorial with AWS Amplify 1.1 and Bootstrap 4.1. So far re-worked through step-05. Stay tuned.
88

99
* [Step 01 - Create a Basic React App with Bootstrap](step-01)
1010
* [Step 02 - Authentication](step-02)
1111
* [Step 03 - Authentication UI](step-03)
12-
* [Step 04 - Everyday Journal](step-04)
13-
* [Step 05 - List of Journals](step-05)
14-
* [Step 06 - Go Live](step-06)
12+
* [Step 04 - User Profile](step-04)
13+
* [Step 05 - State Management via Redux](step-05)
14+
* [Step 06 - Everyday Journal](step-06) (rewrite in progress ...)
15+
* [Step 07 - List of Journals](step-07) (rewrite in progress ...)
16+
* [Step 08 - Go Live](step-08) (rewrite in progress ...)
1517

1618
Go to `journal` sub-folder of each step to check full source code, and run app.
1719

step-03/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,4 +267,4 @@ npm start
267267

268268
<img src="authentication.png" width="480px" />
269269

270-
[Step 04 - Everyday Journal](../step-04)
270+
[Step 04 - User Profile](../step-04)

step-04/README.md

Lines changed: 121 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -1,170 +1,146 @@
1-
# Step 04 - Everyday Journal
1+
# Step 04 - User Profile
22

3-
Now, let's build some features. We are going to build an app that let users store everyday journal.
3+
Let's build a simple user profile page with some user attributes.
44

5-
* [1. User Information](#1-user-information)
6-
* [2. Daily Album](#2-daily-album)
7-
* [3. Write Text](#3-write-text)
8-
* [4. Refresh Album](#4-refresh-album)
9-
* [5. Run App](#5-run-app)
5+
* [1. Create Profile Page](#1-create-profile-page)
6+
* [2. Load User Attributes](#2-load-user-attributes)
7+
* [3. Save User Attributes](#3-save-user-attributes)
8+
* [4. Manage States via Redux](#4-manage-states-via-redux)
109

11-
## 1. User Information
10+
## 1. Create Parofile Page
1211

13-
First we need to make sure every user has his/her own space. Let's get user information first.
12+
Create `src/pages/Profile.jsx`, then add to `<Navigator>`
1413

15-
Import `Auth`
16-
```
17-
import { Auth } from 'aws-amplify';
18-
```
14+
Modify `src/components/Navigator.jsx`
1915

20-
Get current user info
2116
```
22-
componentDidMount() {
23-
Auth.currentUserInfo()
24-
.then(user => this.setState({ user: user })) // we need user.id
25-
.catch(err => console.log(err));
26-
}
27-
```
28-
29-
## 2. Daily Album
17+
const ProfileItems = props => (
18+
<React.Fragment>
19+
<Nav.ItemLink href="#/">
20+
Home
21+
</Nav.ItemLink>
22+
<Nav.ItemLink href="#/profile" active>
23+
Profile
24+
</Nav.ItemLink>
25+
<Nav.ItemLink href="#/login">
26+
Login
27+
<BSpan srOnly>(current}</BSpan>
28+
</Nav.ItemLink>
29+
</React.Fragment>
30+
)
3031
31-
To keep it simple, we organize our journal base on datetime. One day one album. Journal contains image and text.
3232
33-
This can be easily achieved with `S3Album` from `aws-amplify-react`
34-
35-
Imports
36-
```
37-
import { Container, Segment, Header } from 'semantic-ui-react';
38-
import { S3Album } from 'aws-amplify-react';
33+
...
34+
render() {
35+
...
36+
<Route exact path="/profile" component={ProfileItems} />
37+
...
38+
}
3939
```
4040

41-
Today as string
42-
```
43-
const today = () => {
44-
const dt = new Date();
45-
return dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate();
46-
}
47-
```
41+
Add to `<Main>`
4842

49-
Render `S3Album` with userId and date as path in `memberView`
5043
```
51-
memberView() {
52-
const { user } = this.state;
53-
if (!user) { return null; }
54-
55-
const path = user.id + '/' + today() + '/';
56-
return (
57-
<Container>
58-
<Header as="h2" attached="top">{today()}</Header>
59-
<Segment attached>
60-
<S3Album path={path} picker />
61-
</Segment>
62-
</Container>
63-
)
64-
}
44+
<Route
45+
exact
46+
path="/profile"
47+
render={(props) => <Profile user={user} />}
48+
/>
6549
```
6650

67-
## 3. Write Text
51+
<img src="profile.png" width="480px" />
6852

69-
`S3Album` let us select photo, as well as text from device. However as a journal of course need to be able to write something.
53+
## 2. Load User Attributes
7054

71-
Add an input form
72-
```
73-
<Form>
74-
<Form.Input
75-
name="writingTitle"
76-
placeholder="Title"
77-
onChange={this.handleChange}
78-
/>
79-
<Form.TextArea
80-
name="writingContent"
81-
placeholder="Write something ..."
82-
onChange={this.handleChange}
83-
/>
84-
<Form.Button onClick={this.save}>Save</Form.Button>
85-
</Form>
86-
```
55+
We call `Auth.userAttributes` to load attributes. Since we get `user` object from `<Main>` which could be from constructing `<Profile>` component or updating, so we treat both `componentDidMount` and `componentDidUpdate`
8756

88-
Handle input
89-
```
90-
handleChange = (e, { name, value }) => this.setState({ [name]: value });
91-
92-
save() {
93-
const { path, writingTitle, writingContent } = this.state;
94-
const textKey = writingTitle? path + writingTitle.replace(/\s+/g, '_') : null;
95-
const textContent = JSON.stringify({
96-
title: writingTitle,
97-
constent: writingContent
98-
});
99-
this.setState({ textKey: textKey, textContent: textContent });
100-
}
10157
```
58+
componentDidMount() {
59+
if (this.props.user) { this.loadProfile() }
60+
}
10261
103-
Use a hidden S3Text to save content
104-
```
105-
const { user, path, textKey, textContent, ts } = this.state;
106-
if (!user) { return null; }
107-
108-
const key = textKey? textKey + '.json' : null;
109-
110-
render() {
111-
...
112-
113-
<S3Text
114-
hidden
115-
contentType="application/json"
116-
textKey={key}
117-
body={textContent}
118-
onLoad={() => this.setState({ ts: new Date().getTime() })}
119-
/>
120-
121-
...
62+
componentDidUpdate(prevProps) {
63+
if (!prevProps.user && this.props.user) {
64+
this.loadProfile();
12265
}
123-
```
124-
125-
We save text content with title in json format. By default `S3Album` / `S3Text` will display raw json, not very reader friendly. We can add a `translateItem` property to `S3Album`
126-
127-
```
128-
<S3Album
129-
path={path}
130-
ts={ts}
131-
picker
132-
translateItem={this.translateItem}
133-
/>
134-
```
135-
136-
`translateItem` method
137-
```
138-
translateItem(data) {
139-
if ((data.type === 'text') && data.textKey.endsWith('.json')) {
140-
if (!data.content) { return data.content; }
141-
142-
const content = JSON.parse(data.content);
143-
return (
144-
<div>
145-
<h3>{content.title}</h3>
146-
<div>{content.content}</div>
147-
</div>
148-
)
149-
}
150-
return data.content;
66+
}
67+
68+
loadAttributes() {
69+
const { user } = this.props;
70+
Auth.userAttributes(user)
71+
.then(data => this.loadSuccess(data))
72+
.catch(err => this.handleError(err));
73+
}
74+
75+
loadSuccess(data) {
76+
logger.info('loaded user attributes', data);
77+
const profile = this.translateAttributes(data);
78+
this.setState({ profile: profile });
79+
}
80+
81+
handleError(error) {
82+
logger.info('load / save user attributes error', error);
83+
this.setState({ error: error.message || error });
84+
}
85+
86+
translateAttributes(data) {
87+
const profile = {};
88+
data
89+
.filter(attr => ['given_name', 'family_name'].includes(attr.Name))
90+
.forEach(attr => profile[attr.Name] = attr.Value);
91+
return profile;
92+
}
93+
```
94+
95+
`render` the component
96+
```
97+
render() {
98+
const { profile, error } = this.state;
99+
100+
return (
101+
<Container display="flex" flex="column" alignItems="center">
102+
<InputGroup mb="3" style={{ maxWidth: '24rem' }}>
103+
<InputGroup.PrependText>First name</InputGroup.PrependText>
104+
<Form.Input
105+
type="text"
106+
defaultValue={profile.given_name}
107+
onChange={event => this.handleInputChange('given_name', event.target.value)}
108+
/>
109+
</InputGroup>
110+
<InputGroup mb="3" style={{ maxWidth: '24rem' }}>
111+
<InputGroup.PrependText>Last name</InputGroup.PrependText>
112+
<Form.Input
113+
type="text"
114+
defaultValue={profile.family_name}
115+
onChange={event => this.handleInputChange('family_name', event.target.value)}
116+
/>
117+
</InputGroup>
118+
<Button primary px="5" onClick={this.saveProfile}>Save</Button>
119+
{ error && <Alert warning>{error}</Alert> }
120+
</Container>
121+
)
122+
}
123+
```
124+
125+
To keep simple we just cover 'given_name' and 'family_name', which are from 'standard attributes' from Cognito:
126+
[Configuring User Pool Attributes](https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-attributes.html)
127+
128+
## 3. Save User Attributes
129+
130+
For saving, we call `Auth.updateUserAttributes`
131+
132+
```
133+
saveProfile() {
134+
const { user } = this.props;
135+
if (!user) {
136+
this.handleError('No user to save to');
137+
return;
151138
}
152-
```
153-
154-
## 4. Refresh Album
155-
156-
Notice on `S3Text.onLoad` we set a state `ts`. This is to tell `S3Album` to reload so new writing can be displayed in album.
157-
158-
```
159-
<S3Album path={path} ts={ts} picker />
160-
```
161-
162-
## 5. Run App
163139
140+
Auth.updateUserAttributes(user, this.state.profile)
141+
.then(data => this.saveSuccess(data))
142+
.catch(err => this.handleError(err));
143+
}
164144
```
165-
npm start
166-
```
167-
168-
<img src="daily_journal.png" width="360px" />
169145

170-
[Step 05 - List of Journals](../step-05)
146+
[Step 05 - State Management via Redux](../step-05)

step-04/journal/.gitignore

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,12 @@
1919
npm-debug.log*
2020
yarn-debug.log*
2121
yarn-error.log*
22+
23+
#awsmobilejs
24+
appsync-info.json
25+
aws-info.json
26+
project-info.json
27+
aws-exports.*
28+
awsmobilejs/.awsmobile/backend-build
29+
awsmobilejs/\#current-backend-info
30+
~awsmobilejs-*/

0 commit comments

Comments
 (0)