├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── docs ├── README.md ├── assets │ └── img │ │ ├── authenticator.png │ │ ├── daily_journal.png │ │ ├── download_aws_exports.png │ │ ├── host_and_streaming.png │ │ ├── journal_history.png │ │ ├── live.png │ │ ├── login_form.png │ │ └── welcome.png ├── blog │ ├── 2018 │ │ └── 01 │ │ │ ├── 10 │ │ │ └── staging-step.html │ │ │ └── 08 │ │ │ └── why-dochameleon.html │ ├── atom.xml │ ├── feed.xml │ ├── index.html │ └── page1 │ │ └── index.html ├── docs │ ├── assets │ │ └── img │ │ │ ├── authenticator.png │ │ │ ├── daily_journal.png │ │ │ ├── download_aws_exports.png │ │ │ ├── host_and_streaming.png │ │ │ ├── journal_history.png │ │ │ ├── live.png │ │ │ ├── login_form.png │ │ │ └── welcome.png │ ├── step-01-cbra.html │ ├── step-01-home-n-login.html │ ├── step-01-navigator.html │ ├── step-01-react-router.html │ ├── step-01-routing.html │ ├── step-01-run-app.html │ ├── step-01-semantic-ui-react.html │ ├── step-02-authenticator.html │ ├── step-02-aware-of-authstate.html │ ├── step-02-configure.html │ ├── step-02-greetings.html │ ├── step-02-prepare.html │ ├── step-02-run-app.html │ ├── step-03-check-contact.html │ ├── step-03-federated.html │ ├── step-03-login-form.html │ ├── step-03-replace-all.html │ ├── step-03-replace-sign-in.html │ ├── step-03-run-app.html │ ├── step-03-sign-up.html │ ├── step-04-daily-album.html │ ├── step-04-refresh-album.html │ ├── step-04-run-app.html │ ├── step-04-user-info.html │ ├── step-04-write-text.html │ ├── step-05-display-dates.html │ ├── step-05-get-list.html │ ├── step-05-run-app.html │ ├── step-05-switch-date.html │ ├── step-06-build.html │ ├── step-06-publish.html │ └── step-06-touch-ups.html ├── img │ ├── algolia.svg │ ├── dochameleon.png │ ├── f-r.png │ ├── favicon.ico │ ├── favicon.png │ ├── fsts.png │ ├── github.png │ ├── language.svg │ ├── markdown.png │ ├── octocat.jpg │ ├── progressive.png │ ├── react.svg │ ├── setup.png │ └── translation.svg ├── index.html ├── languages.html ├── readme.html └── sitemap.xml ├── step-01 ├── README.md ├── bootstrap-starter.png ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ ├── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ │ ├── Main.jsx │ │ │ ├── Navigator.jsx │ │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ │ ├── Home.jsx │ │ │ ├── Login.jsx │ │ │ └── index.js │ │ └── registerServiceWorker.js │ └── template.md └── starter.png ├── step-02 ├── README.md ├── authenticator.png ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ └── index.js │ │ └── registerServiceWorker.js └── welcome.png ├── step-03 ├── README.md ├── authentication.png ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ ├── auth │ │ │ ├── JConfirmSignIn.jsx │ │ │ ├── JConfirmSignUp.jsx │ │ │ ├── JForgotPassword.jsx │ │ │ ├── JForgotPasswordReset.jsx │ │ │ ├── JSignIn.jsx │ │ │ ├── JSignOut.jsx │ │ │ ├── JSignUp.jsx │ │ │ └── index.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ └── index.js │ │ └── registerServiceWorker.js ├── sign-in.png └── sign-out.png ├── step-04 ├── README.md ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ ├── auth │ │ │ ├── JConfirmSignIn.jsx │ │ │ ├── JConfirmSignUp.jsx │ │ │ ├── JForgotPassword.jsx │ │ │ ├── JForgotPasswordReset.jsx │ │ │ ├── JSignIn.jsx │ │ │ ├── JSignOut.jsx │ │ │ ├── JSignUp.jsx │ │ │ └── index.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── Profile.jsx │ │ └── index.js │ │ └── registerServiceWorker.js ├── profile-edit.png └── profile.png ├── step-05 ├── README.md ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ ├── auth │ │ │ ├── JConfirmSignIn.jsx │ │ │ ├── JConfirmSignUp.jsx │ │ │ ├── JForgotPassword.jsx │ │ │ ├── JForgotPasswordReset.jsx │ │ │ ├── JSignIn.jsx │ │ │ ├── JSignOut.jsx │ │ │ ├── JSignUp.jsx │ │ │ └── index.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── Profile.jsx │ │ └── index.js │ │ ├── registerServiceWorker.js │ │ └── store │ │ ├── AmplifyBridge.js │ │ ├── actions.js │ │ ├── index.js │ │ └── reducers.js └── redux.png ├── step-06 ├── README.md ├── album-render-key.png ├── album_with_picker.png ├── everyday-journal.png ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ ├── Unauthorized.jsx │ │ ├── Unexpected.jsx │ │ ├── album │ │ │ ├── Album.jsx │ │ │ ├── AlbumItem.jsx │ │ │ ├── FilePicker.jsx │ │ │ ├── NoteEditor.jsx │ │ │ └── index.js │ │ ├── auth │ │ │ ├── JConfirmSignIn.jsx │ │ │ ├── JConfirmSignUp.jsx │ │ │ ├── JForgotPassword.jsx │ │ │ ├── JForgotPasswordReset.jsx │ │ │ ├── JSignIn.jsx │ │ │ ├── JSignOut.jsx │ │ │ ├── JSignUp.jsx │ │ │ └── index.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── Profile.jsx │ │ └── index.js │ │ ├── registerServiceWorker.js │ │ └── store │ │ ├── AmplifyBridge.js │ │ ├── actions.js │ │ ├── index.js │ │ └── reducers.js ├── note-editor.png └── s3album.png ├── step-07 ├── README.md ├── journal-by-day.png └── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json │ └── src │ ├── App.css │ ├── App.js │ ├── App.test.js │ ├── components │ ├── Main.jsx │ ├── Navigator.jsx │ ├── Unauthorized.jsx │ ├── Unexpected.jsx │ ├── WhichDay.jsx │ ├── album │ │ ├── Album.jsx │ │ ├── AlbumItem.jsx │ │ ├── FilePicker.jsx │ │ ├── NoteEditor.jsx │ │ └── index.js │ ├── auth │ │ ├── JConfirmSignIn.jsx │ │ ├── JConfirmSignUp.jsx │ │ ├── JForgotPassword.jsx │ │ ├── JForgotPasswordReset.jsx │ │ ├── JSignIn.jsx │ │ ├── JSignOut.jsx │ │ ├── JSignUp.jsx │ │ └── index.js │ └── index.js │ ├── index.css │ ├── index.js │ ├── logo.svg │ ├── pages │ ├── Home.jsx │ ├── Login.jsx │ ├── Profile.jsx │ └── index.js │ ├── registerServiceWorker.js │ └── store │ ├── AmplifyBridge.js │ ├── actions.js │ ├── index.js │ └── reducers.js ├── step-08 ├── README.md ├── journal │ ├── .gitignore │ ├── README.md │ ├── package-lock.json │ ├── package.json │ ├── public │ │ ├── favicon.ico │ │ ├── index.html │ │ └── manifest.json │ └── src │ │ ├── App.css │ │ ├── App.js │ │ ├── App.test.js │ │ ├── components │ │ ├── Main.jsx │ │ ├── Navigator.jsx │ │ ├── Unauthorized.jsx │ │ ├── Unexpected.jsx │ │ ├── WhichDay.jsx │ │ ├── album │ │ │ ├── Album.jsx │ │ │ ├── AlbumItem.jsx │ │ │ ├── FilePicker.jsx │ │ │ ├── NoteEditor.jsx │ │ │ └── index.js │ │ ├── auth │ │ │ ├── JConfirmSignIn.jsx │ │ │ ├── JConfirmSignUp.jsx │ │ │ ├── JForgotPassword.jsx │ │ │ ├── JForgotPasswordReset.jsx │ │ │ ├── JSignIn.jsx │ │ │ ├── JSignOut.jsx │ │ │ ├── JSignUp.jsx │ │ │ └── index.js │ │ └── index.js │ │ ├── index.css │ │ ├── index.js │ │ ├── logo.svg │ │ ├── pages │ │ ├── Home.jsx │ │ ├── Login.jsx │ │ ├── Profile.jsx │ │ └── index.js │ │ ├── registerServiceWorker.js │ │ └── store │ │ ├── AmplifyBridge.js │ │ ├── actions.js │ │ ├── index.js │ │ └── reducers.js └── seattle.png └── website ├── .gitignore ├── blog ├── 2018-01-08-why-dochameleon.md └── 2018-01-10-staging-step.md ├── components ├── docs │ └── DocsLayout.js └── headerLinks.json ├── docs ├── assets │ └── img │ │ ├── authenticator.png │ │ ├── daily_journal.png │ │ ├── download_aws_exports.png │ │ ├── host_and_streaming.png │ │ ├── journal_history.png │ │ ├── live.png │ │ ├── login_form.png │ │ └── welcome.png ├── sidebars.json ├── step-01-cbra.md ├── step-01-home-n-login.md ├── step-01-navigator.md ├── step-01-react-router.md ├── step-01-routing.md ├── step-01-run-app.md ├── step-01-semantic-ui-react.md ├── step-02-authenticator.md ├── step-02-aware-of-authstate.md ├── step-02-configure.md ├── step-02-greetings.md ├── step-02-prepare.md ├── step-02-run-app.md ├── step-03-check-contact.md ├── step-03-federated.md ├── step-03-login-form.md ├── step-03-replace-all.md ├── step-03-replace-sign-in.md ├── step-03-run-app.md ├── step-03-sign-up.md ├── step-04-daily-album.md ├── step-04-refresh-album.md ├── step-04-run-app.md ├── step-04-user-info.md ├── step-04-write-text.md ├── step-05-display-dates.md ├── step-05-get-list.md ├── step-05-run-app.md ├── step-05-switch-date.md ├── step-06-build.md ├── step-06-publish.md └── step-06-touch-ups.md ├── package-lock.json ├── package.json ├── pages ├── README.md ├── index.js └── languages.js ├── siteConfig.js ├── static └── img │ ├── f-r.png │ └── fsts.png └── theme ├── custom.js └── pages.js /.gitignore: -------------------------------------------------------------------------------- 1 | **node_modules** 2 | **aws-exports.js 3 | **awsmobilejs** 4 | 5 | **.DS_Store 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Richard Zhang 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 2 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Journal-AWS-Amplify-Tutorial.svg)]() 3 | [![Gitter Chat](https://badges.gitter.im/aws/aws-amplify.png)](https://gitter.im/AWS-Amplify/Lobby?utm_source=share-link&utm_medium=link&utm_campaign=share-link) 4 | [![Tweet](https://img.shields.io/twitter/url/http/shields.io.svg?style=social)](https://twitter.com/intent/tweet?text=AWS%20Amplify%20Tutorial%0A&url=https://github.com/richardzcode/Journal-AWS-Amplify-Tutorial&hashtags=react,bootstrap,aws) 5 | 6 | # Journal 7 | Step by step tutorial to build a personal journal web app with ReactJS + AWS 8 | 9 | **Update:** This tutorial is rewritten with [AWS Amplify 1.1](https://aws-amplify.github.io/) and [Bootstrap 4.1](https://bootstrap-4-react.com/). 10 | 11 | * [Step 01 - Create a Basic React App with Bootstrap](step-01) 12 | * [Step 02 - Amplify Authentication](step-02) 13 | * [Step 03 - Customize Authentication UI](step-03) 14 | * [Step 04 - User Profile](step-04) 15 | * [Step 05 - State Management via Redux](step-05) 16 | * [Step 06 - Everyday Journal](step-06) 17 | * [Step 07 - List of Journals](step-07) 18 | * [Step 08 - Go Live](step-08) 19 | 20 | Go to `journal` sub-folder of each step to check full source code, and run app. 21 | 22 | 23 | 24 | [Live Demo](https://s3-us-west-1.amazonaws.com/journal-hosting-mobilehub-142591078/index.html#/) 25 | 26 | **Note:** This is a personal experiment. Trying to see what would developers encounter when building a somewhat real app. This tutorial does not cover all aspects of AWS Amplify. 27 | 28 | For most complete and up-to-date documentation, always check [official AWS Amplify site](https://aws-amplify.github.io/). The [Getting Started](https://aws-amplify.github.io/amplify-js/media/quick_start) is very nicely written. 29 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 2 | [![GitHub last commit](https://img.shields.io/github/last-commit/richardzcode/Journal-AWS-Amplify-Tutorial.svg)]() 3 | 4 | # Journal 5 | Step by step tutorial to build a personal journal web app with ReactJS + AWS 6 | 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. 8 | 9 | * [Step 01 - Create a Basic React App with Bootstrap](step-01) 10 | * [Step 02 - Authentication](step-02) 11 | * [Step 03 - Authentication UI](step-03) 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 ...) 17 | 18 | Go to `journal` sub-folder of each step to check full source code, and run app. 19 | 20 | 21 | 22 | [Live Demo](http://journal-hosting-mobilehub-1908112296.s3-website-us-east-1.amazonaws.com/) 23 | [Documentation](https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/) 24 | 25 | **Note:** This is a personal experiment. Trying to see what would developers encounter when building a somewhat real app. This tutorial does not cover all aspects of AWS Amplify. 26 | 27 | For most complete and up-to-date documentation, always check [official AWS Amplify site](https://aws-amplify.github.io/). The [Getting Started](https://aws-amplify.github.io/amplify-js/media/quick_start) is very nicely written. 28 | -------------------------------------------------------------------------------- /docs/assets/img/authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/authenticator.png -------------------------------------------------------------------------------- /docs/assets/img/daily_journal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/daily_journal.png -------------------------------------------------------------------------------- /docs/assets/img/download_aws_exports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/download_aws_exports.png -------------------------------------------------------------------------------- /docs/assets/img/host_and_streaming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/host_and_streaming.png -------------------------------------------------------------------------------- /docs/assets/img/journal_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/journal_history.png -------------------------------------------------------------------------------- /docs/assets/img/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/live.png -------------------------------------------------------------------------------- /docs/assets/img/login_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/login_form.png -------------------------------------------------------------------------------- /docs/assets/img/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/assets/img/welcome.png -------------------------------------------------------------------------------- /docs/blog/atom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog 4 | Journal Tutorial Blog 5 | 2018-01-10T14:00:00Z 6 | Feed for Node.js 7 | 8 | The best place to stay up-to-date with the latest Journal Tutorial news and events. 9 | Copyright © 2018 Richard Zhang 10 | 11 | <![CDATA[Staging Step]]> 12 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/10/staging-step.html 13 | 14 | 15 | 2018-01-10T14:00:00Z 16 | 19 | 20 | Richard Zhang 21 | 22 | 23 | 24 | <![CDATA[Why Dochameleon]]> 25 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/08/why-dochameleon.html 26 | 27 | 28 | 2018-01-08T14:00:00Z 29 | 34 | 35 | Richard Zhang 36 | 37 | 38 | -------------------------------------------------------------------------------- /docs/blog/feed.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Journal Tutorial Blog 5 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog 6 | The best place to stay up-to-date with the latest Journal Tutorial news and events. 7 | Wed, 10 Jan 2018 14:00:00 GMT 8 | http://blogs.law.harvard.edu/tech/rss 9 | Feed for Node.js 10 | Copyright © 2018 Richard Zhang 11 | 12 | <![CDATA[Staging Step]]> 13 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/10/staging-step.html 14 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/10/staging-step.html 15 | Wed, 10 Jan 2018 14:00:00 GMT 16 | 19 | 20 | 21 | <![CDATA[Why Dochameleon]]> 22 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/08/why-dochameleon.html 23 | https://richardzcode.github.io/Journal-AWS-Amplify-Tutorial/blog/2018/01/08/why-dochameleon.html 24 | Mon, 08 Jan 2018 14:00:00 GMT 25 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /docs/docs/assets/img/authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/authenticator.png -------------------------------------------------------------------------------- /docs/docs/assets/img/daily_journal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/daily_journal.png -------------------------------------------------------------------------------- /docs/docs/assets/img/download_aws_exports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/download_aws_exports.png -------------------------------------------------------------------------------- /docs/docs/assets/img/host_and_streaming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/host_and_streaming.png -------------------------------------------------------------------------------- /docs/docs/assets/img/journal_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/journal_history.png -------------------------------------------------------------------------------- /docs/docs/assets/img/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/live.png -------------------------------------------------------------------------------- /docs/docs/assets/img/login_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/login_form.png -------------------------------------------------------------------------------- /docs/docs/assets/img/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/docs/assets/img/welcome.png -------------------------------------------------------------------------------- /docs/img/dochameleon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/dochameleon.png -------------------------------------------------------------------------------- /docs/img/f-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/f-r.png -------------------------------------------------------------------------------- /docs/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/favicon.ico -------------------------------------------------------------------------------- /docs/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/favicon.png -------------------------------------------------------------------------------- /docs/img/fsts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/fsts.png -------------------------------------------------------------------------------- /docs/img/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/github.png -------------------------------------------------------------------------------- /docs/img/language.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/img/markdown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/markdown.png -------------------------------------------------------------------------------- /docs/img/octocat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/octocat.jpg -------------------------------------------------------------------------------- /docs/img/progressive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/progressive.png -------------------------------------------------------------------------------- /docs/img/react.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /docs/img/setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/docs/img/setup.png -------------------------------------------------------------------------------- /docs/img/translation.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /step-01/bootstrap-starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-01/bootstrap-starter.png -------------------------------------------------------------------------------- /step-01/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /step-01/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "bootstrap-4-react": "0.0.53", 7 | "react": "^16.5.2", 8 | "react-dom": "^16.5.2", 9 | "react-router-dom": "^4.3.1", 10 | "react-scripts": "1.1.5" 11 | }, 12 | "scripts": { 13 | "start": "react-scripts start", 14 | "build": "react-scripts build", 15 | "test": "react-scripts test --env=jsdom", 16 | "eject": "react-scripts eject" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /step-01/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-01/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-01/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-01/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-01/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-01/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Navigator, Main } from './components'; 4 | import './App.css'; 5 | 6 | class App extends Component { 7 | render() { 8 | return ( 9 | 10 | 11 |
12 | 13 | ); 14 | } 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /step-01/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-01/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | 5 | import { Home, Login } from '../pages'; 6 | 7 | export default class Main extends Component { 8 | render() { 9 | return ( 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 |
20 | ) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /step-01/journal/src/components/Navigator.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar, Nav, BSpan } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | 5 | const HomeItems = props => ( 6 | 7 | 8 | Home 9 | (current} 10 | 11 | 12 | Login 13 | 14 | 15 | ) 16 | 17 | const LoginItems = props => ( 18 | 19 | 20 | Home 21 | 22 | 23 | Login 24 | (current} 25 | 26 | 27 | ) 28 | 29 | export default class Navigator extends Component { 30 | render() { 31 | return ( 32 | 33 | Journal 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | Greetings 46 | 47 | 48 | ) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /step-01/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | -------------------------------------------------------------------------------- /step-01/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-01/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-01/journal/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step-01/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Home extends Component { 4 | render() { 5 | return ( 6 |

Home

7 | ) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /step-01/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | export default class Login extends Component { 4 | render() { 5 | return ( 6 |

Login

7 | ) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /step-01/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | -------------------------------------------------------------------------------- /step-01/journal/template.md: -------------------------------------------------------------------------------- 1 | ## Bootstrap starter template 2 | 3 | Use this document as a way to quickly start any new project. 4 | All you get is this text and a mostly barebones HTML document. 5 | -------------------------------------------------------------------------------- /step-01/starter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-01/starter.png -------------------------------------------------------------------------------- /step-02/authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-02/authenticator.png -------------------------------------------------------------------------------- /step-02/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | 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-*/ -------------------------------------------------------------------------------- /step-02/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.53", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /step-02/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-02/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-02/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-02/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-02/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-02/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | Amplify.configure(aws_exports); 9 | 10 | class App extends Component { 11 | render() { 12 | return ( 13 | 14 | 15 |
16 | 17 | ); 18 | } 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /step-02/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-02/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Auth, Hub, Logger } from 'aws-amplify'; 5 | 6 | import { Home, Login } from '../pages'; 7 | 8 | const logger = new Logger('Main'); 9 | 10 | export default class Main extends Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.loadUser = this.loadUser.bind(this); 15 | 16 | Hub.listen('auth', this, 'main'); 17 | 18 | this.state = { user: null } 19 | } 20 | 21 | componentDidMount() { 22 | this.loadUser(); 23 | } 24 | 25 | onHubCapsule(capsule) { 26 | logger.info('on Auth event', capsule); 27 | this.loadUser(); 28 | } 29 | 30 | loadUser() { 31 | Auth.currentAuthenticatedUser() 32 | .then(user => this.setState({ user: user })) 33 | .catch(err => this.setState({ user: null })); 34 | } 35 | 36 | render() { 37 | const { user } = this.state; 38 | 39 | return ( 40 | 41 |
42 | 43 | 44 | } 48 | /> 49 | } 53 | /> 54 | 55 | 56 |
57 |
58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /step-02/journal/src/components/Navigator.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar, Nav, BSpan } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Auth, Hub, Logger } from 'aws-amplify'; 5 | import { SignOut } from 'aws-amplify-react'; 6 | 7 | const HomeItems = props => ( 8 | 9 | 10 | Home 11 | (current} 12 | 13 | 14 | Login 15 | 16 | 17 | ) 18 | 19 | const LoginItems = props => ( 20 | 21 | 22 | Home 23 | 24 | 25 | Login 26 | (current} 27 | 28 | 29 | ) 30 | 31 | const logger = new Logger('Navigator'); 32 | 33 | export default class Navigator extends Component { 34 | constructor(props) { 35 | super(props); 36 | 37 | this.loadUser = this.loadUser.bind(this); 38 | 39 | Hub.listen('auth', this, 'navigator'); // Add this component as a listener of auth events. 40 | 41 | this.state = { user: null } 42 | } 43 | 44 | componentDidMount() { 45 | this.loadUser(); // The first check 46 | } 47 | 48 | onHubCapsule(capsule) { 49 | logger.info('on Auth event', capsule); 50 | this.loadUser(); // Triggered every time user sign in / out. 51 | } 52 | 53 | loadUser() { 54 | Auth.currentAuthenticatedUser() 55 | .then(user => this.setState({ user: user })) 56 | .catch(err => this.setState({ user: null })); 57 | } 58 | 59 | render() { 60 | const { user } = this.state; 61 | 62 | return ( 63 | 64 | Journal 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | { user? 'Hi ' + user.username : 'Please sign in' } 78 | 79 | { user && } 80 | 81 | 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /step-02/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | -------------------------------------------------------------------------------- /step-02/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-02/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-02/journal/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step-02/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | 4 | export default class Home extends Component { 5 | render() { 6 | const { user } = this.props; 7 | 8 | return ( 9 | 10 |

Home

11 | { user && You are signed in as {user.username}. } 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /step-02/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | export default class Login extends Component { 6 | render() { 7 | const { user } = this.props; 8 | 9 | return ( 10 | 11 | { !user && } 12 | { user && You are signed in as {user.username}. } 13 | 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /step-02/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | -------------------------------------------------------------------------------- /step-02/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-02/welcome.png -------------------------------------------------------------------------------- /step-03/authentication.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-03/authentication.png -------------------------------------------------------------------------------- /step-03/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | 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-*/ -------------------------------------------------------------------------------- /step-03/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /step-03/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-03/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-03/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-03/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-03/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-03/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 9 | Amplify.configure(aws_exports); 10 | 11 | class App extends Component { 12 | render() { 13 | return ( 14 | 15 | 16 |
17 | 18 | ); 19 | } 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /step-03/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-03/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Auth, Hub, Logger } from 'aws-amplify'; 5 | 6 | import { Home, Login } from '../pages'; 7 | 8 | const logger = new Logger('Main'); 9 | 10 | export default class Main extends Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.loadUser = this.loadUser.bind(this); 15 | 16 | Hub.listen('auth', this, 'main'); 17 | 18 | this.state = { user: null } 19 | } 20 | 21 | componentDidMount() { 22 | this.loadUser(); 23 | } 24 | 25 | onHubCapsule(capsule) { 26 | logger.info('on Auth event', capsule); 27 | this.loadUser(); 28 | } 29 | 30 | loadUser() { 31 | Auth.currentAuthenticatedUser() 32 | .then(user => this.setState({ user: user })) 33 | .catch(err => this.setState({ user: null })); 34 | } 35 | 36 | render() { 37 | const { user } = this.state; 38 | 39 | return ( 40 | 41 |
42 | 43 | 44 | } 48 | /> 49 | } 53 | /> 54 | 55 | 56 |
57 |
58 | ) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /step-03/journal/src/components/Navigator.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Navbar, Nav, BSpan } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Auth, Hub, Logger } from 'aws-amplify'; 5 | import { JSignOut } from './auth'; 6 | 7 | const HomeItems = props => ( 8 | 9 | 10 | Home 11 | (current} 12 | 13 | 14 | Login 15 | 16 | 17 | ) 18 | 19 | const LoginItems = props => ( 20 | 21 | 22 | Home 23 | 24 | 25 | Login 26 | (current} 27 | 28 | 29 | ) 30 | 31 | const logger = new Logger('Navigator'); 32 | 33 | export default class Navigator extends Component { 34 | constructor(props) { 35 | super(props); 36 | 37 | this.loadUser = this.loadUser.bind(this); 38 | 39 | Hub.listen('auth', this, 'navigator'); // Add this component as a listener of auth events. 40 | 41 | this.state = { user: null } 42 | } 43 | 44 | componentDidMount() { 45 | this.loadUser(); // The first check 46 | } 47 | 48 | onHubCapsule(capsule) { 49 | logger.info('on Auth event', capsule); 50 | this.loadUser(); // Triggered every time user sign in / out. 51 | } 52 | 53 | loadUser() { 54 | Auth.currentAuthenticatedUser() 55 | .then(user => this.setState({ user: user })) 56 | .catch(err => this.setState({ user: null })); 57 | } 58 | 59 | render() { 60 | const { user } = this.state; 61 | 62 | return ( 63 | 64 | Journal 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | { user? 'Hi ' + user.username : 'Please sign in' } 78 | 79 | { user && } 80 | 81 | 82 | ) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /step-03/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-03/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-03/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | -------------------------------------------------------------------------------- /step-03/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-03/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-03/journal/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /step-03/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | 4 | export default class Home extends Component { 5 | render() { 6 | const { user } = this.props; 7 | 8 | return ( 9 | 10 |

Home

11 | { user && You are signed in as {user.username}. } 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /step-03/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-03/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | -------------------------------------------------------------------------------- /step-03/sign-in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-03/sign-in.png -------------------------------------------------------------------------------- /step-03/sign-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-03/sign-out.png -------------------------------------------------------------------------------- /step-04/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | 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-*/ -------------------------------------------------------------------------------- /step-04/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "build": "react-scripts build", 17 | "test": "react-scripts test --env=jsdom", 18 | "eject": "react-scripts eject" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /step-04/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-04/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-04/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-04/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-04/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-04/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 9 | Amplify.configure(aws_exports); 10 | 11 | class App extends Component { 12 | render() { 13 | return ( 14 | 15 | 16 |
17 | 18 | ); 19 | } 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /step-04/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-04/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Auth, Hub, Logger } from 'aws-amplify'; 5 | 6 | import { Home, Profile, Login } from '../pages'; 7 | 8 | const logger = new Logger('Main'); 9 | 10 | export default class Main extends Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.loadUser = this.loadUser.bind(this); 15 | 16 | Hub.listen('auth', this, 'main'); 17 | 18 | this.state = { user: null } 19 | } 20 | 21 | componentDidMount() { 22 | this.loadUser(); 23 | } 24 | 25 | onHubCapsule(capsule) { 26 | logger.info('on Auth event', capsule); 27 | this.loadUser(); 28 | } 29 | 30 | loadUser() { 31 | Auth.currentAuthenticatedUser() 32 | .then(user => this.setState({ user: user })) 33 | .catch(err => this.setState({ user: null })); 34 | } 35 | 36 | render() { 37 | const { user } = this.state; 38 | 39 | return ( 40 | 41 |
42 | 43 | 44 | } 48 | /> 49 | } 53 | /> 54 | } 58 | /> 59 | 60 | 61 |
62 |
63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /step-04/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-04/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-04/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | -------------------------------------------------------------------------------- /step-04/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-04/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-04/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | 4 | export default class Home extends Component { 5 | render() { 6 | const { user } = this.props; 7 | 8 | return ( 9 | 10 |

Home

11 | { user && You are signed in as {user.username}. } 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /step-04/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-04/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | export { default as Profile } from './Profile'; 4 | -------------------------------------------------------------------------------- /step-04/profile-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-04/profile-edit.png -------------------------------------------------------------------------------- /step-04/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-04/profile.png -------------------------------------------------------------------------------- /step-05/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | 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-*/ -------------------------------------------------------------------------------- /step-05/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5", 13 | "redux": "^4.0.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /step-05/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-05/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-05/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-05/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-05/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-05/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | import store, { AmplifyBridge } from './store'; 9 | 10 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 11 | Amplify.configure(aws_exports); 12 | 13 | new AmplifyBridge(store); 14 | 15 | class App extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 |
21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /step-05/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-05/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Logger } from 'aws-amplify'; 5 | 6 | import store from '../store'; 7 | import { Home, Profile, Login } from '../pages'; 8 | 9 | const logger = new Logger('Main'); 10 | 11 | export default class Main extends Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.storeListener = this.storeListener.bind(this); 16 | 17 | this.state = { user: null } 18 | } 19 | 20 | componentDidMount() { 21 | this.unsubscribeStore = store.subscribe(this.storeListener); 22 | } 23 | 24 | componentWillUnmount() { 25 | this.unsubscribeStore(); 26 | } 27 | 28 | storeListener() { 29 | logger.info('redux notification'); 30 | this.setState({ user: store.getState().user }); 31 | } 32 | 33 | render() { 34 | const { user } = this.state; 35 | 36 | return ( 37 | 38 |
39 | 40 | 41 | } 45 | /> 46 | } 50 | /> 51 | } 55 | /> 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /step-05/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-05/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-05/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | -------------------------------------------------------------------------------- /step-05/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-05/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-05/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | 4 | export default class Home extends Component { 5 | render() { 6 | const { user } = this.props; 7 | 8 | return ( 9 | 10 |

Home

11 | { user && You are signed in as {user.username}. } 12 |
13 | ) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /step-05/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-05/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | export { default as Profile } from './Profile'; 4 | -------------------------------------------------------------------------------- /step-05/journal/src/store/AmplifyBridge.js: -------------------------------------------------------------------------------- 1 | import { Auth, Hub, Logger } from 'aws-amplify'; 2 | 3 | import { switchUser, updateProfile, deleteProfile } from './actions'; 4 | 5 | const logger = new Logger('AmplifyBridge'); 6 | 7 | export default class AmplifyBridge { 8 | constructor(store) { 9 | this.store = store; 10 | 11 | this.onHubCapsule = this.onHubCapsule.bind(this); 12 | Hub.listen('auth', this, 'AmplifyBridge'); // Add this component as a listener of auth events. 13 | 14 | this.checkUser(); // first check 15 | } 16 | 17 | onHubCapsule(capsule) { 18 | logger.info('on Auth event', capsule); 19 | this.checkUser(); // triggered every time user sign in / out 20 | } 21 | 22 | checkUser() { 23 | Auth.currentAuthenticatedUser() 24 | .then(user => this.checkUserSuccess(user)) 25 | .catch(err => this.checkUserError(err)); 26 | } 27 | 28 | loadProfile(user) { 29 | Auth.userAttributes(user) 30 | .then(data => this.loadProfileSuccess(data)) 31 | .catch(err => this.loadProfileError(err)); 32 | } 33 | 34 | checkUserSuccess(user) { 35 | logger.info('check user success', user); 36 | this.store.dispatch(switchUser(user)); 37 | this.loadProfile(user); 38 | } 39 | 40 | checkUserError(err) { 41 | logger.info('check user error', err); 42 | this.store.dispatch(switchUser(null)); 43 | this.store.dispatch(deleteProfile()); 44 | } 45 | 46 | loadProfileSuccess(data) { 47 | logger.info('load profile success', data); 48 | const profile = this.translateAttributes(data); 49 | this.store.dispatch(updateProfile(profile)); 50 | } 51 | 52 | loadProfileError(err) { 53 | logger.info('load profile error', err); 54 | this.store.dispatch(deleteProfile()); 55 | } 56 | 57 | // Auth.userAttributes returns an array of attributes. 58 | // We map it to an object for easy use. 59 | translateAttributes(data) { 60 | const attributes = {}; 61 | data 62 | .filter(attr => ['given_name', 'family_name'].includes(attr.Name)) 63 | .forEach(attr => attributes[attr.Name] = attr.Value); 64 | return attributes; 65 | } 66 | } 67 | 68 | -------------------------------------------------------------------------------- /step-05/journal/src/store/actions.js: -------------------------------------------------------------------------------- 1 | const SWITCH_USER = 'SWITCH_USER'; 2 | 3 | const UPDATE_PROFILE = 'UPDATE_PROFILE'; 4 | const DELETE_PROFILE = 'DELETE_PROFILE'; 5 | 6 | // when user sign in / out 7 | function switchUser(user) { 8 | return { 9 | type: SWITCH_USER, 10 | user 11 | } 12 | } 13 | 14 | // when user updates profile 15 | function updateProfile(profile) { 16 | return { 17 | type: UPDATE_PROFILE, 18 | profile 19 | } 20 | } 21 | 22 | // when user sign out 23 | function deleteProfile() { 24 | return { type: DELETE_PROFILE } 25 | } 26 | 27 | export { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } 28 | export { switchUser, updateProfile, deleteProfile } 29 | -------------------------------------------------------------------------------- /step-05/journal/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | import { updateProfile } from './actions'; 4 | import Journal from './reducers'; 5 | 6 | const store = createStore(Journal); 7 | 8 | export default store; 9 | export { 10 | updateProfile 11 | } 12 | export { default as AmplifyBridge } from './AmplifyBridge'; 13 | -------------------------------------------------------------------------------- /step-05/journal/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } from './actions'; 4 | 5 | function user(state={}, action) { 6 | switch(action.type) { 7 | case SWITCH_USER: 8 | return action.user; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | function profile(state={}, action) { 15 | switch(action.type) { 16 | case UPDATE_PROFILE: 17 | return Object.assign( 18 | {}, 19 | state, 20 | action.profile 21 | ); 22 | case DELETE_PROFILE: 23 | return null; 24 | default: 25 | return state; 26 | } 27 | } 28 | 29 | const Journal = combineReducers({ 30 | user, 31 | profile 32 | }); 33 | 34 | export default Journal; 35 | -------------------------------------------------------------------------------- /step-05/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-05/redux.png -------------------------------------------------------------------------------- /step-06/album-render-key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/album-render-key.png -------------------------------------------------------------------------------- /step-06/album_with_picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/album_with_picker.png -------------------------------------------------------------------------------- /step-06/everyday-journal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/everyday-journal.png -------------------------------------------------------------------------------- /step-06/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | 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-*/ -------------------------------------------------------------------------------- /step-06/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5", 13 | "redux": "^4.0.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /step-06/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-06/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-06/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-06/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-06/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | import store, { AmplifyBridge } from './store'; 9 | 10 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 11 | Amplify.configure(aws_exports); 12 | 13 | new AmplifyBridge(store); 14 | 15 | class App extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 |
21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /step-06/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-06/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Logger } from 'aws-amplify'; 5 | 6 | import store from '../store'; 7 | import { Home, Profile, Login } from '../pages'; 8 | 9 | const logger = new Logger('Main'); 10 | 11 | export default class Main extends Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.storeListener = this.storeListener.bind(this); 16 | 17 | this.state = { user: null } 18 | } 19 | 20 | componentDidMount() { 21 | this.unsubscribeStore = store.subscribe(this.storeListener); 22 | } 23 | 24 | componentWillUnmount() { 25 | this.unsubscribeStore(); 26 | } 27 | 28 | storeListener() { 29 | logger.info('redux notification'); 30 | this.setState({ user: store.getState().user }); 31 | } 32 | 33 | render() { 34 | const { user } = this.state; 35 | 36 | return ( 37 | 38 |
39 | 40 | 41 | } 45 | /> 46 | } 50 | /> 51 | } 55 | /> 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /step-06/journal/src/components/Unauthorized.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unauthorized extends Component { 5 | render() { 6 | return ( 7 | 8 | Not Authenticated 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-06/journal/src/components/Unexpected.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unexpected extends Component { 5 | render() { 6 | return ( 7 | 8 | Unexpected error happened 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-06/journal/src/components/album/Album.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Card, BDiv } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | import AlbumItem from './AlbumItem'; 6 | import FilePicker from './FilePicker'; 7 | import NoteEditor from './NoteEditor'; 8 | 9 | const logger = new Logger('Album'); 10 | 11 | const Cell = props => ( 12 | 13 | {props.children} 14 | 15 | ) 16 | 17 | export default class Album extends Component { 18 | constructor(props) { 19 | super(props); 20 | this.load = this.load.bind(this); 21 | this.handleUploaded = this.handleUploaded.bind(this); 22 | 23 | this.state = { items: [] } 24 | } 25 | 26 | componentDidMount() { 27 | this.load(); 28 | } 29 | 30 | load() { 31 | const { path } = this.props; 32 | Storage.list(path) 33 | .then(data => this.loadSuccess(data)) 34 | .catch(err => this.loadError(err)); 35 | } 36 | 37 | loadSuccess(data) { 38 | logger.info('load album success', data); 39 | this.setState({ items: data }); 40 | } 41 | 42 | loadError(err) { 43 | logger.info('load album error', err); 44 | } 45 | 46 | handleUploaded(key) { 47 | this.load(); 48 | } 49 | 50 | render() { 51 | const { path } = this.props; 52 | const { items } = this.state; 53 | 54 | return ( 55 | 56 | 57 | { items.map(item => { 58 | return ( 59 | 60 | 61 | 62 | ) 63 | })} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /step-06/journal/src/components/album/AlbumItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BImg, BPre, BH5, BDiv } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('AlbumItem'); 6 | 7 | const Note = props => { 8 | const note = JSON.parse(props.json); 9 | return ( 10 | 11 | {note.subject} 12 | 13 | {note.content} 14 | 15 | 16 | ) 17 | } 18 | 19 | export default class AlbumItem extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.load = this.load.bind(this); 23 | 24 | this.state = { url: null, json: null } 25 | } 26 | 27 | componentDidMount() { 28 | this.load(); 29 | } 30 | 31 | load() { 32 | const { item } = this.props; 33 | if (item.key.endsWith('.json')) { 34 | Storage.get(item.key, { download: true }) 35 | .then(data => this.loadJsonSuccess(data)) 36 | .catch(err => this.loadError(err)); 37 | } else { 38 | Storage.get(item.key) 39 | .then(url => this.loadImageSuccess(url)) 40 | .catch(err => this.loadError(err)); 41 | } 42 | } 43 | 44 | loadJsonSuccess(data) { 45 | logger.info('load album item success', data); 46 | this.setState({ json: data.Body.toString('utf8') }); 47 | } 48 | 49 | loadImageSuccess(url) { 50 | logger.info('load album item success', url); 51 | this.setState({ url: url }); 52 | } 53 | 54 | loadError(err) { 55 | logger.info('load album item error', err); 56 | } 57 | 58 | render() { 59 | const { url, json } = this.state; 60 | 61 | return ( 62 | 63 | { json && } 64 | { url && } 65 | 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /step-06/journal/src/components/album/FilePicker.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Form, Button } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('FilePicker'); 6 | 7 | const style = { 8 | position: 'relative', 9 | height: '3rem', 10 | button: { 11 | width: '100%', 12 | height: '100%', 13 | display: 'inline-block', 14 | position: 'absolute', 15 | left: 0, 16 | top: 0 17 | }, 18 | file: { 19 | width: '100%', 20 | height: '100%', 21 | display: 'inline-block', 22 | position: 'absolute', 23 | left: 0, 24 | top: 0, 25 | opacity: 0, 26 | cursor: 'pointer' 27 | } 28 | } 29 | 30 | export default class FilePicker extends Component { 31 | constructor(props) { 32 | super(props); 33 | this.handleChange = this.handleChange.bind(this); 34 | } 35 | 36 | handleChange(event) { 37 | const file = event.target.files[0]; 38 | logger.info('picked file', file); 39 | // Clear file input 40 | window.document.getElementById('fileInputForm').reset(); 41 | 42 | this.uploadFile(file); 43 | } 44 | 45 | uploadFile(file) { 46 | const { path } = this.props; 47 | if (!path) { 48 | logger.warn('missing path property for FilePicker'); 49 | return; 50 | } 51 | 52 | const key = this.calcS3Key(file); 53 | const { type } = file; 54 | Storage.put(key, file, { contentType: type }) 55 | .then(data => this.uploadFileSuccess(data)) 56 | .catch(err => this.uploadFileError(err)); 57 | } 58 | 59 | uploadFileSuccess(data) { 60 | logger.info('upload file success', data); 61 | const { onUploaded } = this.props; 62 | if (onUploaded) { onUploaded(data.key); } 63 | } 64 | 65 | uploadFileError(err) { 66 | logger.info('upload file error', err); 67 | } 68 | 69 | calcS3Key(file) { 70 | return this.props.path + encodeURI(file.name).replace(/\s/g, '_'); 71 | } 72 | 73 | render() { 74 | return ( 75 |
76 |
77 | 78 | 83 | 84 |
85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /step-06/journal/src/components/album/NoteEditor.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Card, Form, Button } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('NoteEditor'); 6 | 7 | export default class NoteEditor extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.handleSave = this.handleSave.bind(this); 11 | this.inputs = { 12 | subject: '', 13 | content: '', 14 | s3Key: props.s3Key || '' 15 | }; 16 | } 17 | 18 | handleSave() { 19 | const { subject, content } = this.inputs; 20 | const note = { subject, content }; 21 | logger.info('saving note', note); 22 | 23 | const key = this.getS3Key(); 24 | Storage.put(key, JSON.stringify(note), { contentType: 'application/json' }) 25 | .then(data => this.uploadNoteSuccess(data)) 26 | .catch(err => this.uploadNoteError(err)); 27 | } 28 | 29 | uploadNoteSuccess(data) { 30 | logger.info('upload note success', data); 31 | const { onUploaded } = this.props; 32 | if (onUploaded) { onUploaded(data.key); } 33 | } 34 | 35 | uploadNoteError(err) { 36 | logger.info('upload note error', err); 37 | } 38 | 39 | getS3Key() { 40 | if (!this.inputs.s3Key) { 41 | this.inputs.s3Key = this.props.path + new Date().getTime() + '.json'; 42 | } 43 | return this.inputs.s3Key; 44 | } 45 | 46 | render() { 47 | return ( 48 | 49 | 50 | this.inputs.subject = event.target.value} 56 | /> 57 | this.inputs.content = event.target.value} 62 | /> 63 | 64 | 65 | 66 | 67 | 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /step-06/journal/src/components/album/index.js: -------------------------------------------------------------------------------- 1 | export { default as Album } from './Album'; 2 | -------------------------------------------------------------------------------- /step-06/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-06/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-06/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | 4 | export { default as Unexpected } from './Unexpected'; 5 | export { default as Unauthorized } from './Unauthorized'; 6 | -------------------------------------------------------------------------------- /step-06/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-06/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-06/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Unexpected, Unauthorized } from '../components'; 4 | import { Album } from '../components/album'; 5 | 6 | const padding = n => { 7 | return n > 9 ? n : '0' + n; 8 | } 9 | 10 | const today = () => { 11 | const dt = new date(); 12 | return [ 13 | dt.getfullyear(), 14 | padding(dt.getmonth() + 1), 15 | padding(dt.getdate()) 16 | ].join('-'); 17 | } 18 | 19 | const album_path = user_id => { 20 | return user_id + '/' + today() + '/'; 21 | } 22 | 23 | export default class Home extends Component { 24 | render() { 25 | const { user } = this.props; 26 | 27 | if (!user) { return } 28 | if (!user.id) { return } 29 | 30 | return ( 31 | 32 | 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-06/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-06/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | export { default as Profile } from './Profile'; 4 | -------------------------------------------------------------------------------- /step-06/journal/src/store/actions.js: -------------------------------------------------------------------------------- 1 | const SWITCH_USER = 'SWITCH_USER'; 2 | 3 | const UPDATE_PROFILE = 'UPDATE_PROFILE'; 4 | const DELETE_PROFILE = 'DELETE_PROFILE'; 5 | 6 | // when user sign in / out 7 | function switchUser(user) { 8 | return { 9 | type: SWITCH_USER, 10 | user 11 | } 12 | } 13 | 14 | // when user updates profile 15 | function updateProfile(profile) { 16 | return { 17 | type: UPDATE_PROFILE, 18 | profile 19 | } 20 | } 21 | 22 | // when user sign out 23 | function deleteProfile() { 24 | return { type: DELETE_PROFILE } 25 | } 26 | 27 | export { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } 28 | export { switchUser, updateProfile, deleteProfile } 29 | -------------------------------------------------------------------------------- /step-06/journal/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | import { updateProfile } from './actions'; 4 | import Journal from './reducers'; 5 | 6 | const store = createStore(Journal); 7 | 8 | export default store; 9 | export { 10 | updateProfile 11 | } 12 | export { default as AmplifyBridge } from './AmplifyBridge'; 13 | -------------------------------------------------------------------------------- /step-06/journal/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } from './actions'; 4 | 5 | function user(state={}, action) { 6 | switch(action.type) { 7 | case SWITCH_USER: 8 | return action.user; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | function profile(state={}, action) { 15 | switch(action.type) { 16 | case UPDATE_PROFILE: 17 | return Object.assign( 18 | {}, 19 | state, 20 | action.profile 21 | ); 22 | case DELETE_PROFILE: 23 | return null; 24 | default: 25 | return state; 26 | } 27 | } 28 | 29 | const Journal = combineReducers({ 30 | user, 31 | profile 32 | }); 33 | 34 | export default Journal; 35 | -------------------------------------------------------------------------------- /step-06/note-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/note-editor.png -------------------------------------------------------------------------------- /step-06/s3album.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-06/s3album.png -------------------------------------------------------------------------------- /step-07/journal-by-day.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-07/journal-by-day.png -------------------------------------------------------------------------------- /step-07/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /step-07/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.6", 7 | "aws-amplify-react": "^2.0.7", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5", 13 | "redux": "^4.0.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /step-07/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-07/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-07/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Bootstrap 4 React 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-07/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-07/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-07/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | import store, { AmplifyBridge } from './store'; 9 | 10 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 11 | Amplify.configure(aws_exports); 12 | 13 | new AmplifyBridge(store); 14 | 15 | class App extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 |
21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /step-07/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-07/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Logger } from 'aws-amplify'; 5 | 6 | import store from '../store'; 7 | import { Home, Profile, Login } from '../pages'; 8 | 9 | const logger = new Logger('Main'); 10 | 11 | export default class Main extends Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.storeListener = this.storeListener.bind(this); 16 | 17 | this.state = { user: null } 18 | } 19 | 20 | componentDidMount() { 21 | this.unsubscribeStore = store.subscribe(this.storeListener); 22 | } 23 | 24 | componentWillUnmount() { 25 | this.unsubscribeStore(); 26 | } 27 | 28 | storeListener() { 29 | logger.info('redux notification'); 30 | this.setState({ user: store.getState().user }); 31 | } 32 | 33 | render() { 34 | const { user } = this.state; 35 | 36 | return ( 37 | 38 |
39 | 40 | 41 | } 45 | /> 46 | } 50 | /> 51 | } 55 | /> 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /step-07/journal/src/components/Unauthorized.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unauthorized extends Component { 5 | render() { 6 | return ( 7 | 8 | Not Authenticated 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-07/journal/src/components/Unexpected.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unexpected extends Component { 5 | render() { 6 | return ( 7 | 8 | Unexpected error happened 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-07/journal/src/components/WhichDay.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Row, Col, BH4, Dropdown } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const padding = n => { 6 | return n > 9 ? n : '0' + n; 7 | } 8 | 9 | const today = () => { 10 | const dt = new Date(); 11 | return [ 12 | dt.getFullYear(), 13 | padding(dt.getMonth() + 1), 14 | padding(dt.getDate()) 15 | ].join('-'); 16 | } 17 | 18 | const logger = new Logger('WhichDay'); 19 | 20 | export default class WhichDay extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.load = this.load.bind(this); 24 | this.setDay = this.setDay.bind(this); 25 | this.state = { day: today(), days: [today()] } 26 | } 27 | 28 | componentDidMount() { 29 | this.load(); 30 | } 31 | 32 | load() { 33 | const { rootPath } = this.props; 34 | Storage.list(rootPath) 35 | .then(data => this.loadSuccess(data)) 36 | .catch(err => this.loadError(err)); 37 | } 38 | 39 | loadSuccess(data) { 40 | logger.info('load list success', data); 41 | const days = data.map(item => { 42 | const match = item.key.match(/\/(\d{4}-\d{1,2}-\d{1,2})\//); 43 | return match? match[1] : null; 44 | }) 45 | .filter (day => !!day); 46 | const day_set = new Set([today()].concat(days)); 47 | const unique_days = Array.from(day_set).sort().reverse(); 48 | this.setState({ days: unique_days }); 49 | } 50 | 51 | loadError(err) { 52 | logger.info('load list error', err); 53 | } 54 | 55 | setDay(day) { 56 | this.setState({ day: day }); 57 | 58 | const { onDaySelected } = this.props; 59 | if (onDaySelected) { onDaySelected(day); } 60 | } 61 | 62 | render() { 63 | const { day, days } = this.state; 64 | 65 | return ( 66 | 67 | 68 | {day} 69 | 70 | 71 | 72 | {day} 73 | 74 | { days.map(day => { 75 | return ( 76 | this.setDay(day)} 80 | > 81 | {day} 82 | 83 | ) 84 | })} 85 | 86 | 87 | 88 | 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /step-07/journal/src/components/album/Album.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Card, BDiv } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | import AlbumItem from './AlbumItem'; 6 | import FilePicker from './FilePicker'; 7 | import NoteEditor from './NoteEditor'; 8 | 9 | const logger = new Logger('Album'); 10 | 11 | const Cell = props => ( 12 | 13 | {props.children} 14 | 15 | ) 16 | 17 | export default class Album extends Component { 18 | constructor(props) { 19 | super(props); 20 | this.load = this.load.bind(this); 21 | this.handleUploaded = this.handleUploaded.bind(this); 22 | 23 | this.state = { items: [] } 24 | } 25 | 26 | componentDidMount() { 27 | this.load(); 28 | } 29 | 30 | load() { 31 | const { path } = this.props; 32 | Storage.list(path) 33 | .then(data => this.loadSuccess(data)) 34 | .catch(err => this.loadError(err)); 35 | } 36 | 37 | loadSuccess(data) { 38 | logger.info('load album success', data); 39 | this.setState({ items: data }); 40 | } 41 | 42 | loadError(err) { 43 | logger.info('load album error', err); 44 | } 45 | 46 | handleUploaded(key) { 47 | this.load(); 48 | } 49 | 50 | render() { 51 | const { path } = this.props; 52 | const { items } = this.state; 53 | 54 | return ( 55 | 56 | 57 | { items.map(item => { 58 | return ( 59 | 60 | 61 | 62 | ) 63 | })} 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | ) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /step-07/journal/src/components/album/AlbumItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BImg, BPre, BH5, BDiv } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('AlbumItem'); 6 | 7 | const Note = props => { 8 | const note = JSON.parse(props.json); 9 | return ( 10 | 11 | {note.subject} 12 | 13 | {note.content} 14 | 15 | 16 | ) 17 | } 18 | 19 | export default class AlbumItem extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.load = this.load.bind(this); 23 | 24 | this.state = { url: null, json: null } 25 | } 26 | 27 | componentDidMount() { 28 | this.load(); 29 | } 30 | 31 | load() { 32 | const { item } = this.props; 33 | if (item.key.endsWith('.json')) { 34 | Storage.get(item.key, { download: true }) 35 | .then(data => this.loadJsonSuccess(data)) 36 | .catch(err => this.loadError(err)); 37 | } else { 38 | Storage.get(item.key) 39 | .then(url => this.loadImageSuccess(url)) 40 | .catch(err => this.loadError(err)); 41 | } 42 | } 43 | 44 | loadJsonSuccess(data) { 45 | logger.info('load album item success', data); 46 | this.setState({ json: data.Body.toString('utf8') }); 47 | } 48 | 49 | loadImageSuccess(url) { 50 | logger.info('load album item success', url); 51 | this.setState({ url: url }); 52 | } 53 | 54 | loadError(err) { 55 | logger.info('load album item error', err); 56 | } 57 | 58 | render() { 59 | const { url, json } = this.state; 60 | 61 | return ( 62 | 63 | { json && } 64 | { url && } 65 | 66 | ) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /step-07/journal/src/components/album/FilePicker.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Form, Button } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('FilePicker'); 6 | 7 | const style = { 8 | position: 'relative', 9 | height: '3rem', 10 | button: { 11 | width: '100%', 12 | height: '100%', 13 | display: 'inline-block', 14 | position: 'absolute', 15 | left: 0, 16 | top: 0 17 | }, 18 | file: { 19 | width: '100%', 20 | height: '100%', 21 | display: 'inline-block', 22 | position: 'absolute', 23 | left: 0, 24 | top: 0, 25 | opacity: 0, 26 | cursor: 'pointer' 27 | } 28 | } 29 | 30 | export default class FilePicker extends Component { 31 | constructor(props) { 32 | super(props); 33 | this.handleChange = this.handleChange.bind(this); 34 | } 35 | 36 | handleChange(event) { 37 | const file = event.target.files[0]; 38 | logger.info('picked file', file); 39 | // Clear file input 40 | window.document.getElementById('fileInputForm').reset(); 41 | 42 | this.uploadFile(file); 43 | } 44 | 45 | uploadFile(file) { 46 | const { path } = this.props; 47 | if (!path) { 48 | logger.warn('missing path property for FilePicker'); 49 | return; 50 | } 51 | 52 | const key = this.calcS3Key(file); 53 | const { type } = file; 54 | Storage.put(key, file, { contentType: type }) 55 | .then(data => this.uploadFileSuccess(data)) 56 | .catch(err => this.uploadFileError(err)); 57 | } 58 | 59 | uploadFileSuccess(data) { 60 | logger.info('upload file success', data); 61 | const { onUploaded } = this.props; 62 | if (onUploaded) { onUploaded(data.key); } 63 | } 64 | 65 | uploadFileError(err) { 66 | logger.info('upload file error', err); 67 | } 68 | 69 | calcS3Key(file) { 70 | return this.props.path + encodeURI(file.name).replace(/\s/g, '_'); 71 | } 72 | 73 | render() { 74 | return ( 75 |
76 |
77 | 78 | 83 | 84 |
85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /step-07/journal/src/components/album/NoteEditor.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Card, Form, Button } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('NoteEditor'); 6 | 7 | export default class NoteEditor extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.handleSave = this.handleSave.bind(this); 11 | this.inputs = { 12 | subject: '', 13 | content: '', 14 | s3Key: props.s3Key || '' 15 | }; 16 | } 17 | 18 | handleSave() { 19 | const { subject, content } = this.inputs; 20 | const note = { subject, content }; 21 | logger.info('saving note', note); 22 | 23 | const key = this.getS3Key(); 24 | Storage.put(key, JSON.stringify(note), { contentType: 'application/json' }) 25 | .then(data => this.uploadNoteSuccess(data)) 26 | .catch(err => this.uploadNoteError(err)); 27 | } 28 | 29 | uploadNoteSuccess(data) { 30 | logger.info('upload note success', data); 31 | const { onUploaded } = this.props; 32 | if (onUploaded) { onUploaded(data.key); } 33 | } 34 | 35 | uploadNoteError(err) { 36 | logger.info('upload note error', err); 37 | } 38 | 39 | getS3Key() { 40 | if (!this.inputs.s3Key) { 41 | this.inputs.s3Key = this.props.path + new Date().getTime() + '.json'; 42 | } 43 | return this.inputs.s3Key; 44 | } 45 | 46 | render() { 47 | return ( 48 | 49 | 50 | this.inputs.subject = event.target.value} 56 | /> 57 | this.inputs.content = event.target.value} 62 | /> 63 | 64 | 65 | 66 | 67 | 68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /step-07/journal/src/components/album/index.js: -------------------------------------------------------------------------------- 1 | export { default as Album } from './Album'; 2 | -------------------------------------------------------------------------------- /step-07/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-07/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-07/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | export { default as WhichDay } from './WhichDay'; 4 | 5 | export { default as Unexpected } from './Unexpected'; 6 | export { default as Unauthorized } from './Unauthorized'; 7 | -------------------------------------------------------------------------------- /step-07/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-07/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-07/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Unexpected, Unauthorized, WhichDay } from '../components'; 4 | import { Album } from '../components/album'; 5 | 6 | const padding = n => { 7 | return n > 9 ? n : '0' + n; 8 | } 9 | 10 | const today = () => { 11 | const dt = new Date(); 12 | return [ 13 | dt.getFullYear(), 14 | padding(dt.getMonth() + 1), 15 | padding(dt.getDate()) 16 | ].join('-'); 17 | } 18 | 19 | const album_path = (user_id, day) => { 20 | return user_id + '/' + day + '/'; 21 | } 22 | 23 | export default class Home extends Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { day: today() } 27 | } 28 | 29 | render() { 30 | const { user } = this.props; 31 | const { day } = this.state; 32 | 33 | if (!user) { return } 34 | if (!user.id) { return } 35 | 36 | return ( 37 | 38 | this.setState({ day: day })} /> 39 | 40 | 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /step-07/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-07/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | export { default as Profile } from './Profile'; 4 | -------------------------------------------------------------------------------- /step-07/journal/src/store/actions.js: -------------------------------------------------------------------------------- 1 | const SWITCH_USER = 'SWITCH_USER'; 2 | 3 | const UPDATE_PROFILE = 'UPDATE_PROFILE'; 4 | const DELETE_PROFILE = 'DELETE_PROFILE'; 5 | 6 | // when user sign in / out 7 | function switchUser(user) { 8 | return { 9 | type: SWITCH_USER, 10 | user 11 | } 12 | } 13 | 14 | // when user updates profile 15 | function updateProfile(profile) { 16 | return { 17 | type: UPDATE_PROFILE, 18 | profile 19 | } 20 | } 21 | 22 | // when user sign out 23 | function deleteProfile() { 24 | return { type: DELETE_PROFILE } 25 | } 26 | 27 | export { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } 28 | export { switchUser, updateProfile, deleteProfile } 29 | -------------------------------------------------------------------------------- /step-07/journal/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | import { updateProfile } from './actions'; 4 | import Journal from './reducers'; 5 | 6 | const store = createStore(Journal); 7 | 8 | export default store; 9 | export { 10 | updateProfile 11 | } 12 | export { default as AmplifyBridge } from './AmplifyBridge'; 13 | -------------------------------------------------------------------------------- /step-07/journal/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } from './actions'; 4 | 5 | function user(state={}, action) { 6 | switch(action.type) { 7 | case SWITCH_USER: 8 | return action.user; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | function profile(state={}, action) { 15 | switch(action.type) { 16 | case UPDATE_PROFILE: 17 | return Object.assign( 18 | {}, 19 | state, 20 | action.profile 21 | ); 22 | case DELETE_PROFILE: 23 | return null; 24 | default: 25 | return state; 26 | } 27 | } 28 | 29 | const Journal = combineReducers({ 30 | user, 31 | profile 32 | }); 33 | 34 | export default Journal; 35 | -------------------------------------------------------------------------------- /step-08/README.md: -------------------------------------------------------------------------------- 1 | # Step 08 - Go Live 2 | 3 | Now let's finalize and go live 4 | 5 | * [1. Touch Ups](#1-touch-ups) 6 | * [2. Build](#2-build) 7 | * [3. Publish](#3-publish) 8 | 9 | ## 1. Touch Ups 10 | 11 | Change icon, app title, and some small updates. 12 | 13 | Not necessary to explain details in this step. Just check out the code should be fine. 14 | 15 | ## 2. Build 16 | 17 | ``` 18 | npm run build 19 | ``` 20 | 21 | The command generate everything needed into `build/` folder. 22 | 23 | Remember to set `homepage` in your `package.json` if publishing to a [relative path](https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#building-for-relative-paths) 24 | 25 | ## 3. Publish 26 | 27 | This app is built completely in HTML/JS/CSS, no backend server needed. Just a static file hosting will do. Here we choose Amazon S3. 28 | 29 | In [Step 02](../step-02) we created an AWS Mobile Hub project. During that step we have created everything that we needed for this app. 30 | 31 | Go to AWS Console -> Mobile Hub. Click `Resources` on the top-right corner. Look for "Amazon S3 Buckets", then find the bucket that has `hosting` inside its name. 32 | 33 | Upload files under `build/` folder to this bucket. Then open in browser. We are [live!](https://s3-us-west-1.amazonaws.com/journal-hosting-mobilehub-142591078/index.html) 34 | 35 | 36 | -------------------------------------------------------------------------------- /step-08/journal/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /step-08/journal/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "journal", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.1.1", 7 | "aws-amplify-react": "^2.0.2", 8 | "bootstrap-4-react": "0.0.54", 9 | "react": "^16.5.2", 10 | "react-dom": "^16.5.2", 11 | "react-router-dom": "^4.3.1", 12 | "react-scripts": "1.1.5", 13 | "redux": "^4.0.0" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test --env=jsdom", 19 | "eject": "react-scripts eject" 20 | }, 21 | "homepage": "https://s3-us-west-1.amazonaws.com/journal-hosting-mobilehub-142591078" 22 | } 23 | -------------------------------------------------------------------------------- /step-08/journal/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-08/journal/public/favicon.ico -------------------------------------------------------------------------------- /step-08/journal/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 23 | Journal 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /step-08/journal/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Bootstrap 4 React", 3 | "name": "Bootstrap 4 React Documentation", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /step-08/journal/src/App.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-top: 5rem; 3 | } 4 | .starter-template { 5 | padding: 3rem 1.5rem; 6 | text-align: center; 7 | } 8 | -------------------------------------------------------------------------------- /step-08/journal/src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Amplify from 'aws-amplify'; 3 | 4 | import { Navigator, Main } from './components'; 5 | import './App.css'; 6 | import aws_exports from './aws-exports'; 7 | 8 | import store, { AmplifyBridge } from './store'; 9 | 10 | Amplify.Logger.LOG_LEVEL = 'INFO'; // We write INFO level logs throughout app 11 | Amplify.configure(aws_exports); 12 | 13 | new AmplifyBridge(store); 14 | 15 | class App extends Component { 16 | render() { 17 | return ( 18 | 19 | 20 |
21 | 22 | ); 23 | } 24 | } 25 | 26 | export default App; 27 | -------------------------------------------------------------------------------- /step-08/journal/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /step-08/journal/src/components/Main.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | import { HashRouter, Route, Switch } from 'react-router-dom'; 4 | import { Logger } from 'aws-amplify'; 5 | 6 | import store from '../store'; 7 | import { Home, Profile, Login } from '../pages'; 8 | 9 | const logger = new Logger('Main'); 10 | 11 | export default class Main extends Component { 12 | constructor(props) { 13 | super(props); 14 | 15 | this.storeListener = this.storeListener.bind(this); 16 | 17 | this.state = { user: null } 18 | } 19 | 20 | componentDidMount() { 21 | this.unsubscribeStore = store.subscribe(this.storeListener); 22 | } 23 | 24 | componentWillUnmount() { 25 | this.unsubscribeStore(); 26 | } 27 | 28 | storeListener() { 29 | logger.info('redux notification'); 30 | this.setState({ user: store.getState().user }); 31 | } 32 | 33 | render() { 34 | const { user } = this.state; 35 | 36 | return ( 37 | 38 |
39 | 40 | 41 | } 45 | /> 46 | } 50 | /> 51 | } 55 | /> 56 | 57 | 58 |
59 |
60 | ) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /step-08/journal/src/components/Unauthorized.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unauthorized extends Component { 5 | render() { 6 | return ( 7 | 8 | Not Authenticated 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-08/journal/src/components/Unexpected.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Container } from 'bootstrap-4-react'; 3 | 4 | export default class Unexpected extends Component { 5 | render() { 6 | return ( 7 | 8 | Unexpected error happened 9 | 10 | ) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /step-08/journal/src/components/WhichDay.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Row, Col, BH4, Dropdown } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const padding = n => { 6 | return n > 9 ? n : '0' + n; 7 | } 8 | 9 | const today = () => { 10 | const dt = new Date(); 11 | return [ 12 | dt.getFullYear(), 13 | padding(dt.getMonth() + 1), 14 | padding(dt.getDate()) 15 | ].join('-'); 16 | } 17 | 18 | const logger = new Logger('WhichDay'); 19 | 20 | export default class WhichDay extends Component { 21 | constructor(props) { 22 | super(props); 23 | this.load = this.load.bind(this); 24 | this.setDay = this.setDay.bind(this); 25 | this.state = { day: today(), days: [today()] } 26 | } 27 | 28 | componentDidMount() { 29 | this.load(); 30 | } 31 | 32 | load() { 33 | const { rootPath } = this.props; 34 | Storage.list(rootPath) 35 | .then(data => this.loadSuccess(data)) 36 | .catch(err => this.loadError(err)); 37 | } 38 | 39 | loadSuccess(data) { 40 | logger.info('load list success', data); 41 | const days = data.map(item => { 42 | const match = item.key.match(/\/(\d{4}-\d{1,2}-\d{1,2})\//); 43 | return match? match[1] : null; 44 | }) 45 | .filter (day => !!day); 46 | const day_set = new Set([today()].concat(days)); 47 | const unique_days = Array.from(day_set).sort().reverse(); 48 | this.setState({ days: unique_days }); 49 | } 50 | 51 | loadError(err) { 52 | logger.info('load list error', err); 53 | } 54 | 55 | setDay(day) { 56 | this.setState({ day: day }); 57 | 58 | const { onDaySelected } = this.props; 59 | if (onDaySelected) { onDaySelected(day); } 60 | } 61 | 62 | render() { 63 | const { day, days } = this.state; 64 | 65 | return ( 66 | 67 | 68 | {day} 69 | 70 | 71 | 72 | {day} 73 | 74 | { days.map(day => { 75 | return ( 76 | this.setDay(day)} 80 | > 81 | {day} 82 | 83 | ) 84 | })} 85 | 86 | 87 | 88 | 89 | ) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /step-08/journal/src/components/album/AlbumItem.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { BImg, BPre, BH5, BDiv } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('AlbumItem'); 6 | 7 | const Note = props => { 8 | const note = JSON.parse(props.json); 9 | return ( 10 | 11 | {note.subject} 12 | 13 | {note.content} 14 | 15 | 16 | ) 17 | } 18 | 19 | export default class AlbumItem extends Component { 20 | constructor(props) { 21 | super(props); 22 | this.load = this.load.bind(this); 23 | this.handleClick = this.handleClick.bind(this); 24 | 25 | this.state = { url: null, json: null } 26 | } 27 | 28 | componentDidMount() { 29 | this.load(); 30 | } 31 | 32 | load() { 33 | const { item } = this.props; 34 | if (item.key.endsWith('.json')) { 35 | Storage.get(item.key, { download: true }) 36 | .then(data => this.loadJsonSuccess(data)) 37 | .catch(err => this.loadError(err)); 38 | } else { 39 | Storage.get(item.key) 40 | .then(url => this.loadImageSuccess(url)) 41 | .catch(err => this.loadError(err)); 42 | } 43 | } 44 | 45 | loadJsonSuccess(data) { 46 | logger.info('load album item success', data); 47 | this.setState({ json: data.Body.toString('utf8') }); 48 | } 49 | 50 | loadImageSuccess(url) { 51 | logger.info('load album item success', url); 52 | this.setState({ url: url }); 53 | } 54 | 55 | loadError(err) { 56 | logger.info('load album item error', err); 57 | } 58 | 59 | handleClick() { 60 | const { item, onClick } = this.props; 61 | if (onClick) { 62 | onClick(item.key); 63 | } 64 | } 65 | 66 | render() { 67 | const { url, json } = this.state; 68 | 69 | return ( 70 | 71 | { json && } 72 | { url && } 73 | 74 | ) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /step-08/journal/src/components/album/FilePicker.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Form, Button } from 'bootstrap-4-react'; 3 | import { Storage, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('FilePicker'); 6 | 7 | const style = { 8 | position: 'relative', 9 | height: '3rem', 10 | button: { 11 | width: '100%', 12 | height: '100%', 13 | display: 'inline-block', 14 | position: 'absolute', 15 | left: 0, 16 | top: 0 17 | }, 18 | file: { 19 | width: '100%', 20 | height: '100%', 21 | display: 'inline-block', 22 | position: 'absolute', 23 | left: 0, 24 | top: 0, 25 | opacity: 0, 26 | cursor: 'pointer' 27 | } 28 | } 29 | 30 | export default class FilePicker extends Component { 31 | constructor(props) { 32 | super(props); 33 | this.handleChange = this.handleChange.bind(this); 34 | } 35 | 36 | handleChange(event) { 37 | const file = event.target.files[0]; 38 | logger.info('picked file', file); 39 | // Clear file input 40 | window.document.getElementById('fileInputForm').reset(); 41 | 42 | this.uploadFile(file); 43 | } 44 | 45 | uploadFile(file) { 46 | const { path } = this.props; 47 | if (!path) { 48 | logger.warn('missing path property for FilePicker'); 49 | return; 50 | } 51 | 52 | const key = this.calcS3Key(file); 53 | const { type } = file; 54 | Storage.put(key, file, { contentType: type }) 55 | .then(data => this.uploadFileSuccess(data)) 56 | .catch(err => this.uploadFileError(err)); 57 | } 58 | 59 | uploadFileSuccess(data) { 60 | logger.info('upload file success', data); 61 | const { onUploaded } = this.props; 62 | if (onUploaded) { onUploaded(data.key); } 63 | } 64 | 65 | uploadFileError(err) { 66 | logger.info('upload file error', err); 67 | } 68 | 69 | calcS3Key(file) { 70 | return this.props.path + encodeURI(file.name).replace(/\s/g, '_'); 71 | } 72 | 73 | render() { 74 | return ( 75 |
76 |
77 | 78 | 83 | 84 |
85 | ) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /step-08/journal/src/components/album/index.js: -------------------------------------------------------------------------------- 1 | export { default as Album } from './Album'; 2 | -------------------------------------------------------------------------------- /step-08/journal/src/components/auth/JSignOut.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button } from 'bootstrap-4-react'; 3 | import { Auth, Logger } from 'aws-amplify'; 4 | 5 | const logger = new Logger('JSignOut'); 6 | 7 | export default class JSignOut extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.signOut = this.signOut.bind(this); 11 | } 12 | 13 | signOut() { 14 | Auth.signOut() 15 | .then(() => logger.info('sign out success')) 16 | .catch(err => logger.info('sign out error', err)); 17 | } 18 | 19 | render() { 20 | return ( 21 | 31 | ) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /step-08/journal/src/components/auth/index.js: -------------------------------------------------------------------------------- 1 | export { default as JSignOut } from './JSignOut'; 2 | export { default as JSignIn } from './JSignIn'; 3 | export { default as JConfirmSignIn } from './JConfirmSignIn'; 4 | export { default as JSignUp } from './JSignUp'; 5 | export { default as JConfirmSignUp } from './JConfirmSignUp'; 6 | export { default as JForgotPassword } from './JForgotPassword'; 7 | export { default as JForgotPasswordReset } from './JForgotPasswordReset'; 8 | -------------------------------------------------------------------------------- /step-08/journal/src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigator } from './Navigator'; 2 | export { default as Main } from './Main'; 3 | export { default as WhichDay } from './WhichDay'; 4 | 5 | export { default as Unexpected } from './Unexpected'; 6 | export { default as Unauthorized } from './Unauthorized'; 7 | -------------------------------------------------------------------------------- /step-08/journal/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | -------------------------------------------------------------------------------- /step-08/journal/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render(, document.getElementById('root')); 6 | -------------------------------------------------------------------------------- /step-08/journal/src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | import { Unexpected, Unauthorized, WhichDay } from '../components'; 4 | import { Album } from '../components/album'; 5 | 6 | const padding = n => { 7 | return n > 9 ? n : '0' + n; 8 | } 9 | 10 | const today = () => { 11 | const dt = new Date(); 12 | return [ 13 | dt.getFullYear(), 14 | padding(dt.getMonth() + 1), 15 | padding(dt.getDate()) 16 | ].join('-'); 17 | } 18 | 19 | const album_path = (user_id, day) => { 20 | return user_id + '/' + day + '/'; 21 | } 22 | 23 | export default class Home extends Component { 24 | constructor(props) { 25 | super(props); 26 | this.state = { day: today() } 27 | } 28 | 29 | render() { 30 | const { user } = this.props; 31 | const { day } = this.state; 32 | 33 | if (!user) { return } 34 | if (!user.id) { return } 35 | 36 | return ( 37 | 38 | this.setState({ day: day })} /> 39 | 40 | 41 | ) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /step-08/journal/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Lead, BSpan } from 'bootstrap-4-react'; 3 | import { Authenticator } from 'aws-amplify-react'; 4 | 5 | import { 6 | JSignIn, 7 | JConfirmSignIn, 8 | JSignUp, 9 | JConfirmSignUp, 10 | JForgotPassword, 11 | JForgotPasswordReset 12 | } from '../components/auth'; 13 | 14 | const CustomAuthenticator = props => ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ) 24 | 25 | export default class Login extends Component { 26 | render() { 27 | const { user } = this.props; 28 | 29 | return ( 30 | 31 | { !user && } 32 | { user && You are signed in as {user.username}. } 33 | 34 | ) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /step-08/journal/src/pages/index.js: -------------------------------------------------------------------------------- 1 | export { default as Home } from './Home'; 2 | export { default as Login } from './Login'; 3 | export { default as Profile } from './Profile'; 4 | -------------------------------------------------------------------------------- /step-08/journal/src/store/actions.js: -------------------------------------------------------------------------------- 1 | const SWITCH_USER = 'SWITCH_USER'; 2 | 3 | const UPDATE_PROFILE = 'UPDATE_PROFILE'; 4 | const DELETE_PROFILE = 'DELETE_PROFILE'; 5 | 6 | // when user sign in / out 7 | function switchUser(user) { 8 | return { 9 | type: SWITCH_USER, 10 | user 11 | } 12 | } 13 | 14 | // when user updates profile 15 | function updateProfile(profile) { 16 | return { 17 | type: UPDATE_PROFILE, 18 | profile 19 | } 20 | } 21 | 22 | // when user sign out 23 | function deleteProfile() { 24 | return { type: DELETE_PROFILE } 25 | } 26 | 27 | export { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } 28 | export { switchUser, updateProfile, deleteProfile } 29 | -------------------------------------------------------------------------------- /step-08/journal/src/store/index.js: -------------------------------------------------------------------------------- 1 | import { createStore } from 'redux'; 2 | 3 | import { updateProfile } from './actions'; 4 | import Journal from './reducers'; 5 | 6 | const store = createStore(Journal); 7 | 8 | export default store; 9 | export { 10 | updateProfile 11 | } 12 | export { default as AmplifyBridge } from './AmplifyBridge'; 13 | -------------------------------------------------------------------------------- /step-08/journal/src/store/reducers.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | 3 | import { SWITCH_USER, UPDATE_PROFILE, DELETE_PROFILE } from './actions'; 4 | 5 | function user(state={}, action) { 6 | switch(action.type) { 7 | case SWITCH_USER: 8 | return action.user; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | function profile(state={}, action) { 15 | switch(action.type) { 16 | case UPDATE_PROFILE: 17 | return Object.assign( 18 | {}, 19 | state, 20 | action.profile 21 | ); 22 | case DELETE_PROFILE: 23 | return null; 24 | default: 25 | return state; 26 | } 27 | } 28 | 29 | const Journal = combineReducers({ 30 | user, 31 | profile 32 | }); 33 | 34 | export default Journal; 35 | -------------------------------------------------------------------------------- /step-08/seattle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/step-08/seattle.png -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | build/ 4 | yarn.lock 5 | **/node_modules 6 | searchConfig.js 7 | -------------------------------------------------------------------------------- /website/blog/2018-01-08-why-dochameleon.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Why Dochameleon 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | It is not hard to guess where the idea of Dochameleon is from. 8 | 9 | With [Docusaurus](https://github.com/facebook/Docusaurus) built and maintained by awesome team from Facebook, why do I want to build another one? 10 | 11 | One personal reason is I am so excited about Docusaurus. Can't stop checking around, rewrite, restruct source code. So only way to satisfy is to create a new project. 12 | 13 | 14 | 15 | Here are some technical reasons, 16 | 17 | 1. Dev server and site generation are written separately. Inconsistency is inevitable. 18 | 2. Pages can not share building blocks. 19 | 3. Big CSS file makes styling hard. 20 | 21 | At the same time some features are removed. I feel they are a bit too opinionated with complexity, may not suited for all open source projects. 22 | 23 | 1. Search with [algolia](https://www.algolia.com/). 24 | 2. Multi-Language with [crowdin](https://crowdin.com/). 25 | 3. Project version support. 26 | 27 | For example, at the end of the day multi-language and versioning are grouped by file hierarchies. One options is to just take pull requests on GitHub. 28 | -------------------------------------------------------------------------------- /website/blog/2018-01-10-staging-step.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Staging Step 3 | author: Richard Zhang 4 | authorGitHub: richardzcode 5 | --- 6 | 7 | The basic idea of Dochameleon is to generate a static web server by using [Server-Side-Rendering](https://reactjs.org/docs/react-dom-server.html). Contents are combined from core library and specific project. 8 | 9 | Combining from two sources can be tricky. So there is a staging step. Which in essence copies files from the two sources into one place, for SSR to generate from, and for developers to inspect on. 10 | 11 | The one place can be found at `node_modules/dochameleon/lib/stage`, contains a file structure like this: 12 | 13 | ```bash 14 | node_modules/dochameleon/lib/stage/ 15 | ├── Blog.js 16 | ├── Docs.js 17 | ├── Pages.js 18 | ├── blog 19 | │   └── 2018-01-08-why-dochameleon.md 20 | │   └── 2018-01-10-stage-step.md 21 | ├── components 22 | │   ├── Button.js 23 | │   ├── CollapseIcon.js 24 | │   ├── FeatureCallout.js 25 | │   ├── FeatureCallouts.js 26 | │   ├── Features.js 27 | │   ├── Footer.js 28 | │   ├── Head.js 29 | │   ├── HeaderNav.js 30 | │   ├── HelpDetails.js 31 | │   ├── HomeSplash.js 32 | │   ├── MarkdownBlock.js 33 | │   ├── Page.js 34 | │   ├── Showcase.js 35 | │   ├── SideNav.js 36 | │   ├── blog 37 | │   ├── docs 38 | │   ├── featureCallouts.json 39 | │   ├── features.json 40 | │   ├── help.json 41 | │   └── users.json 42 | ├── dfs.js 43 | ├── docs 44 | │   ├── doc1.md 45 | │   ├── doc2.md 46 | │   ├── doc3.md 47 | │   ├── exampledoc4.md 48 | │   ├── exampledoc5.md 49 | │   └── sidebars.json 50 | ├── pages 51 | │   ├── help.js 52 | │   ├── index.js 53 | │   └── users.js 54 | ├── parse 55 | │   ├── Markdown.js 56 | │   └── toSlug.js 57 | ├── static 58 | │   ├── css 59 | │   └── img 60 | └── theme 61 | ├── blog.js 62 | ├── main.js 63 | ├── markdown.js 64 | └── pages.js 65 | ``` 66 | -------------------------------------------------------------------------------- /website/components/docs/DocsLayout.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const Page = require('../Page.js'); 5 | const Button = require('../Button.js'); 6 | const Doc = require('./Doc.js'); 7 | const DocsSidebar = require('./DocsSidebar.js'); 8 | 9 | const PrevNext = props => { 10 | const { site, metadata, lang } = props; 11 | const { theme } = site; 12 | if (!metadata.previous && !metadata.next) { return null; } 13 | 14 | return ( 15 |
16 | {metadata.previous && ( 17 | 20 | )} 21 |   22 | {metadata.next && ( 23 | 26 | )} 27 |
28 | ) 29 | } 30 | 31 | class DocsLayout extends React.Component { 32 | render() { 33 | const { site, metadata, lang } = this.props; 34 | const { theme } = site; 35 | const { title, brief } = metadata; 36 | const content = this.props.children; 37 | const url = site.urlWithRoot(site.docUrl(metadata.id)); 38 | 39 | return ( 40 | 41 | 42 | { metadata.sidebar 43 | ? ( 44 | 45 | 46 | 47 | 48 | 49 | {content} 50 | 51 | 52 | 53 | ) 54 | : ( 55 | 56 | {content} 57 | 58 | 59 | ) 60 | } 61 | 62 | 63 | ); 64 | } 65 | } 66 | module.exports = DocsLayout; 67 | -------------------------------------------------------------------------------- /website/components/headerLinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "type": "doc", "value": "step-01-cra", "label": "Steps" }, 3 | { 4 | "type": "url", 5 | "value": "https://github.com/richardzcode/Journal-AWS-Amplify-Tutorial", 6 | "img": "img/github.png", 7 | "label": "GitHub" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /website/docs/assets/img/authenticator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/authenticator.png -------------------------------------------------------------------------------- /website/docs/assets/img/daily_journal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/daily_journal.png -------------------------------------------------------------------------------- /website/docs/assets/img/download_aws_exports.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/download_aws_exports.png -------------------------------------------------------------------------------- /website/docs/assets/img/host_and_streaming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/host_and_streaming.png -------------------------------------------------------------------------------- /website/docs/assets/img/journal_history.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/journal_history.png -------------------------------------------------------------------------------- /website/docs/assets/img/live.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/live.png -------------------------------------------------------------------------------- /website/docs/assets/img/login_form.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/login_form.png -------------------------------------------------------------------------------- /website/docs/assets/img/welcome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/docs/assets/img/welcome.png -------------------------------------------------------------------------------- /website/docs/sidebars.json: -------------------------------------------------------------------------------- 1 | { 2 | "Steps": { 3 | "Step 01 - Create a Basic React App": [ 4 | "step-01-cbra", 5 | "step-01-react-router", 6 | "step-01-navigator", 7 | "step-01-routing", 8 | "step-01-home-n-login", 9 | "step-01-run-app" 10 | ], 11 | "Step 02 - Authentication": [ 12 | "step-02-prepare", 13 | "step-02-configure", 14 | "step-02-authenticator", 15 | "step-02-greetings", 16 | "step-02-aware-of-authstate", 17 | "step-02-run-app" 18 | ], 19 | "Step 03 - Authentication UI": [ 20 | "step-03-replace-sign-in", 21 | "step-03-login-form", 22 | "step-03-federated", 23 | "step-03-check-contact", 24 | "step-03-sign-up", 25 | "step-03-replace-all", 26 | "step-03-run-app" 27 | ], 28 | "Step 04 - Everyday Journal": [ 29 | "step-04-user-info", 30 | "step-04-daily-album", 31 | "step-04-write-text", 32 | "step-04-refresh-album", 33 | "step-04-run-app" 34 | ], 35 | "Step 05 - List of Journals": [ 36 | "step-05-get-list", 37 | "step-05-display-dates", 38 | "step-05-switch-date", 39 | "step-05-run-app" 40 | ], 41 | "Step 06 - Go Live": [ 42 | "step-06-touch-ups", 43 | "step-06-build", 44 | "step-06-publish" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /website/docs/step-01-cbra.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-cbra 3 | title: Step 01 - Create a Basic React App with Bootstrap 4 | sidebar_label: Create a Bootstrap React App 5 | --- 6 | 7 | # Create Bootstrap React App 8 | 9 | `create-bootstrap-react-app` creates basic React App with a Bootstrap starter template. 10 | 11 | If not already installed, run 12 | ``` 13 | npm install --global create-react-app 14 | npm install --global create-bootstrap-react-app 15 | ``` 16 | 17 | Then create the app 18 | ``` 19 | create-bootstrap-react-app journal 20 | cd journal 21 | npm start 22 | ``` 23 | -------------------------------------------------------------------------------- /website/docs/step-01-home-n-login.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-home-n-login 3 | title: Step 01 - Create a Basic React App with Bootstrap 4 | sidebar_label: Home and Login Page 5 | --- 6 | 7 | ## 5. Create Home and Login Page 8 | 9 | We don't have Home and Login page yet. Let's create them. 10 | 11 | `src/pages/Home.jsx` 12 | ``` 13 | import React, { Component } from 'react'; 14 | 15 | export default class Home extends Component { 16 | render() { 17 | return ( 18 |

Home

19 | ) 20 | } 21 | } 22 | ``` 23 | 24 | `src/pages/Login.jsx` 25 | ``` 26 | import React, { Component } from 'react'; 27 | 28 | export default class Home extends Component { 29 | render() { 30 | return ( 31 |

Login

32 | ) 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /website/docs/step-01-navigator.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-navigator 3 | title: Step 01 - Create a Basic React App with Bootstrap 4 | sidebar_label: Adjust Navigator 5 | --- 6 | 7 | # Adjust Navigator 8 | 9 | We don't need everything from starter template. Let's adjust. Assume we start with a Home page and a Login page. 10 | 11 | Open `src/components/Navigator.jsx` 12 | 13 | * Update `` content to 'Journal'. 14 | * Remove 'Disabled' and 'Dropdown' menu items. 15 | * Update 'Link' to 'Login'. 16 | * Replace search with 'Greetings' text. 17 | 18 | With `react-router` components. `Navigator.jsx` become, 19 | 20 | ``` 21 | import React, { Component } from 'react'; 22 | import { Navbar, Nav, BSpan } from 'bootstrap-4-react'; 23 | import { HashRouter, Route, Switch } from 'react-router-dom'; 24 | 25 | const HomeItems = props => ( 26 | 27 | 28 | Home 29 | (current} 30 | 31 | 32 | Login 33 | 34 | 35 | ) 36 | 37 | const LoginItems = props => ( 38 | 39 | 40 | Home 41 | 42 | 43 | Login 44 | (current} 45 | 46 | 47 | ) 48 | 49 | export default class Navigator extends Component { 50 | render() { 51 | return ( 52 | 53 | Journal 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | Greetings 66 | 67 | 68 | ) 69 | } 70 | } 71 | ``` 72 | -------------------------------------------------------------------------------- /website/docs/step-01-react-router.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-react-router 3 | title: Step 01 - Create a Basic React App with Bootstrap 4 | sidebar_label: Add React Router 5 | --- 6 | 7 | # Add React Router 8 | Let's use [react-router](https://github.com/ReactTraining/react-router) for routing. 9 | ``` 10 | npm install --save react-router-dom 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/step-01-routing.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-routing 3 | title: Step 01 - Create a Basic React App with Bootstrap 4 | sidebar_label: Page Routing 5 | --- 6 | 7 | # Page Routing 8 | 9 | Modify `src/components/Main.jsx` to route to Home or Login page. 10 | 11 | ``` 12 | import React, { Component } from 'react'; 13 | import { Container } from 'bootstrap-4-react'; 14 | import { HashRouter, Route, Switch } from 'react-router-dom'; 15 | 16 | import { Home, Login } from '../pages'; 17 | 18 | export default class Main extends Component { 19 | render() { 20 | return ( 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
31 | ) 32 | } 33 | } 34 | ``` 35 | -------------------------------------------------------------------------------- /website/docs/step-01-run-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-run-app 3 | title: Step 01 - Create a Basic React App 4 | sidebar_label: Run App 5 | --- 6 | 7 | # Run App 8 | 9 | ``` 10 | npm start 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/step-01-semantic-ui-react.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-01-semantic-ui-react 3 | title: Step 01 - Create a Basic React App 4 | sidebar_label: Add Semantic UI React 5 | --- 6 | 7 | # Add Semantic UI React 8 | 9 | Let's use [Semantic UI React](https://react.semantic-ui.com) for nicer looking UI. 10 | ``` 11 | npm install --save semantic-ui-react 12 | ``` 13 | 14 | There are a few ways to add CSS for Semantic UI, here is one way: 15 | ``` 16 | npm install --save semantic-ui-css 17 | ``` 18 | 19 | Then open `src/index.js`, add 20 | ``` 21 | import 'semantic-ui-css/semantic.min.css'; 22 | ``` 23 | -------------------------------------------------------------------------------- /website/docs/step-02-authenticator.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-authenticator 3 | title: Step 02 - Authentication 4 | sidebar_label: Add Authenticator 5 | --- 6 | 7 | # Add Authenticator 8 | 9 | Open `src/modules/Login.jsx`, change content to: 10 | ``` 11 | import React, { Component } from 'react'; 12 | 13 | import { Authenticator } from 'aws-amplify-react'; 14 | 15 | export default class Login extends Component { 16 | render() { 17 | return 18 | } 19 | } 20 | ``` 21 | 22 | Now `npm start`. Login becomes real 23 | 24 | 25 | 26 | Got ahead sign up and sign in. Create a test user. 27 | -------------------------------------------------------------------------------- /website/docs/step-02-aware-of-authstate.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-aware-of-authstate 3 | title: Step 02 - Authentication 4 | sidebar_label: Home Page Aware of authState 5 | --- 6 | 7 | # Home Page Aware of authState 8 | 9 | Now sign in works. How does Home page know if an user is signed in or not? 10 | 11 | We have `Greetings` now. So just listen to its `onStateChange` event, then pass to Home component in router. 12 | 13 | In `src/App.js` 14 | ``` 15 | constructor(props) { 16 | ... 17 | this.handleStateChange = this.handleStateChange.bind(this); 18 | } 19 | 20 | handleStateChange(authState, authData) { 21 | this.setState({ 22 | authState: authState, 23 | authData: authData 24 | }); 25 | } 26 | 27 | 28 | ``` 29 | 30 | ``` 31 | 'Hi ' + username} 35 | onStateChange={this.handleStateChange} 36 | /> 37 | ``` 38 | 39 | ``` 40 | ( 41 | 42 | )}/> 43 | ``` 44 | 45 | Then, in `src/modules/Home.jsx`, just check `authState` property 46 | ``` 47 | render() { 48 | const { authState, authData } = this.props; 49 | return ( 50 |
51 |
Home
52 |
{authState}
53 |
54 | ); 55 | } 56 | ``` 57 | -------------------------------------------------------------------------------- /website/docs/step-02-configure.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-configure 3 | title: Step 02 - Authentication 4 | sidebar_label: Configure AWS Amplify 5 | --- 6 | 7 | # Configure AWS Amplify 8 | 9 | Open `src/App.js`, add these lines 10 | ``` 11 | import Amplify from 'aws-amplify'; 12 | import aws_exports from './aws-exports'; 13 | 14 | Amplify.configure(aws_exports); 15 | ``` 16 | -------------------------------------------------------------------------------- /website/docs/step-02-greetings.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-greetings 3 | title: Step 02 - Authentication 4 | sidebar_label: Greetings 5 | --- 6 | 7 | # Greetings 8 | 9 | Notice after sign in, there is a sign out button. It makes sense to have sign out button. However in our case the place is not right. 10 | 11 | **Hide Greetings** 12 | 13 | Let's hide this one. `src/modules/Login.jsx` becomes: 14 | ``` 15 | import React, { Component } from 'react'; 16 | 17 | import { Authenticator, Greetings } from 'aws-amplify-react'; 18 | 19 | export default class Login extends Component { 20 | render() { 21 | return 22 | } 23 | } 24 | ``` 25 | 26 | `Authenticator` is composed of a group of pieces, `Greetings` is one of them. `hide` defines a list of pieces to be hidden. 27 | 28 | **Greetings on Menu** 29 | 30 | What we actually want is greetings on the top-right corner. Let's edit `src/App.js` to add menu item with Greetings. 31 | 32 | First import Greetings 33 | ``` 34 | import { Greetings } from 'aws-amplify-react'; 35 | ``` 36 | 37 | The default styling doesn't fit in our UI, lets add the menu item and remove default theme of Greetings. 38 | ``` 39 | const GreetingsTheme = { 40 | navBar: { 41 | }, 42 | navRight: { 43 | }, 44 | navButton: { 45 | border: '0', 46 | background: 'white', 47 | color: 'blue', 48 | borderBottom: '1px solid', 49 | fontSize: '0.8em' 50 | } 51 | } 52 | 53 | ... 54 | 55 | 56 | 57 | 58 | 59 | 60 | ``` 61 | 62 | **Custom Greetings** 63 | 64 | Change the greetings 65 | ``` 66 | 'Hi ' + username} 70 | /> 71 | ``` 72 | 73 | 74 | -------------------------------------------------------------------------------- /website/docs/step-02-prepare.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-prepare 3 | title: Step 02 - Authentication 4 | sidebar_label: Prepare 5 | --- 6 | 7 | # Prepare 8 | 9 | ### Library 10 | 11 | Install package, core library and react specific. 12 | ``` 13 | npm install --save aws-amplify 14 | npm install --save aws-amplify-react 15 | ``` 16 | 17 | ### Service 18 | 19 | Create a AWS Mobile Hub project with [awsmobile-CLI](https://github.com/aws/awsmobile-cli) 20 | 21 | ``` 22 | npm install -g awsmobile-cli 23 | 24 | awsmobile init 25 | awsmobile user-signin enable 26 | awsmobile user-files enable 27 | awsmobile push 28 | ``` 29 | -------------------------------------------------------------------------------- /website/docs/step-02-run-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-02-run-app 3 | title: Step 02 - Authentication 4 | sidebar_label: Run App 5 | --- 6 | 7 | # Run App 8 | 9 | ``` 10 | npm start 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/step-03-check-contact.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-check-contact 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Check Contact Verification 5 | --- 6 | 7 | # Check Contact Verification 8 | 9 | User may forget password. In order to be able to recover password, user has to have one of the contact info verified. We should prompt user about this. 10 | 11 | Update `src/components/LoginForm`: 12 | ``` 13 | constructor(props) { 14 | super(props); 15 | 16 | this.checkContat = this.checkContact.bind(this); 17 | this.signIn = this.signIn.bind(this); 18 | } 19 | 20 | checkContact(user) { 21 | Auth.verifiedContact(user) 22 | .then(data => { 23 | if (!JS.isEmpty(data.verified)) { 24 | this.changeState('signedIn', user); 25 | } else { 26 | user = Object.assign(user, data); 27 | this.changeState('verifyContact', user); 28 | } 29 | }); 30 | } 31 | 32 | signIn() { 33 | const { username, password } = this.inputs; 34 | logger.debug('username: ' + username); 35 | Auth.signIn(username, password) 36 | .then(user => this.checkContact(user)) 37 | .catch(err => this.error(err)); 38 | } 39 | ``` 40 | -------------------------------------------------------------------------------- /website/docs/step-03-federated.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-federated 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Federated Sign In 5 | --- 6 | 7 | # Federated Sign In 8 | 9 | `withFederated` HOC turns buttons into Federated sign in button. 10 | 11 | * Build UI 12 | - style your own button 13 | - trigger `props.facebookSignIn | props.googleSignIn | props.amazonSignIn` at the right time 14 | * Transform UI 15 | - withFederated(...) 16 | * Render UI 17 | - pass in `federated` property with app ids 18 | - handle `onStateChange` to notify sign in event 19 | 20 | ``` 21 | import { AuthPiece, withFederated } from 'aws-amplify-react'; 22 | 23 | const FederatedButtons = (props) => ( 24 |
25 | 31 |
32 | ); 33 | 34 | const Federated = withFederated(FederatedButtons); 35 | 36 | const federated_data = { 37 | google_client_id: '', 38 | facebook_app_id: '__replace_with_your_facebook_app_id__', 39 | amazon_client_id: '' 40 | }; 41 | 42 | ... 43 | 44 | // in login form render method, 45 | // trigger AuthPiece.handleAuthStateChange when state changes, i.e. signed in 46 | 47 | 48 | ``` 49 | -------------------------------------------------------------------------------- /website/docs/step-03-replace-all.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-replace-all 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Replace all Auth components 5 | --- 6 | 7 | # Replace all Auth components 8 | 9 | In process of replacing all Auth components. Here are a couple small things. 10 | 11 | **Semantic UI radio button** 12 | 13 | The radio button from Semantic UI fires `onChange` event on label as target. Which will cause an issue in collecting state. The actual radio button state is passed as the second parameter. We need to translate it before calling `this.handleInputChange` 14 | 15 | For example in `src/components/VerifyContactForm.js` 16 | ``` 17 | this.handleInputChange({ target: semantic_data })} 21 | /> 22 | this.handleInputChange({ target: semantic_data })} 26 | /> 27 | ``` 28 | 29 | **hideDefault** 30 | 31 | In order to replace default Auth forms, we provide `hide` list to `Authenticator`. Once all reaplaced, we could simply pass a `hideDefault` property, no need to write the whole list. 32 | 33 | ``` 34 | 35 | ``` 36 | -------------------------------------------------------------------------------- /website/docs/step-03-replace-sign-in.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-replace-sign-in 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Replace Sign In 5 | --- 6 | 7 | # Replace Sign In 8 | 9 | Let's add a Semantic UI [LoginForm](https://react.semantic-ui.com/layouts/login). 10 | 11 | Save the form to `src/components/LoginForm.js`. 12 | 13 | Then modify `src/modules/Login.jsx`, hide the default SignIn, add our LoginForm 14 | ``` 15 | import { Authenticator, Greetings, SignIn } from 'aws-amplify-react'; 16 | import { LoginForm } from '../components'; 17 | 18 | render() { 19 | return ( 20 | 21 | 22 | 23 | ) 24 | } 25 | ``` 26 | Now, looks better 27 | 28 | 29 | -------------------------------------------------------------------------------- /website/docs/step-03-run-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-run-app 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Run App 5 | --- 6 | 7 | # Run App 8 | 9 | ``` 10 | npm start 11 | ``` 12 | -------------------------------------------------------------------------------- /website/docs/step-03-sign-up.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-03-sign-up 3 | title: Step 03 - Authentication UI 4 | sidebar_label: Sign Up 5 | --- 6 | 7 | # Sign Up 8 | 9 | LoginForm has a 'Sign Up' link. On click it should show Sign Up form. 10 | 11 | This can be achieved by `changeState()` method from `AuthPiece`. The method notifies `Authenticator` state change, and then `Authenticator` notify all the Auth Pieces it contains to render properly. 12 | 13 | ``` 14 | 15 | New to us? this.changeState('signUp')}>Sign Up 16 | 17 | ``` 18 | 19 | Now on click we'll see Sign Up form, but it is the default form. Go through the same process create a RegisterForm. Same to other UI components in auth flow. 20 | -------------------------------------------------------------------------------- /website/docs/step-04-daily-album.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-04-daily-album 3 | title: Step 04 - Everyday Journal 4 | sidebar_label: Daily Album 5 | --- 6 | 7 | # Daily Album 8 | 9 | To keep it simple, we organize our journal base on datetime. One day one album. Journal contains image and text. 10 | 11 | This can be easily achieved with `S3Album` from `aws-amplify-react` 12 | 13 | Imports 14 | ``` 15 | import { Container, Segment, Header } from 'semantic-ui-react'; 16 | import { S3Album } from 'aws-amplify-react'; 17 | ``` 18 | 19 | Today as string 20 | ``` 21 | const today = () => { 22 | const dt = new Date(); 23 | return dt.getFullYear() + '-' + (dt.getMonth() + 1) + '-' + dt.getDate(); 24 | } 25 | ``` 26 | 27 | Render `S3Album` with userId and date as path in `memberView` 28 | ``` 29 | memberView() { 30 | const { user } = this.state; 31 | if (!user) { return null; } 32 | 33 | const path = user.id + '/' + today() + '/'; 34 | return ( 35 | 36 |
{today()}
37 | 38 | 39 | 40 |
41 | ) 42 | } 43 | ``` 44 | -------------------------------------------------------------------------------- /website/docs/step-04-refresh-album.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-04-refresh-album 3 | title: Step 04 - Everyday Journal 4 | sidebar_label: Refresh Album 5 | --- 6 | 7 | # Refresh Album 8 | 9 | Notice on `S3Text.onLoad` we set a state `ts`. This is to tell `S3Album` to reload so new writing can be displayed in album. 10 | 11 | ``` 12 | 13 | ``` 14 | -------------------------------------------------------------------------------- /website/docs/step-04-run-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-04-run-app 3 | title: Step 04 - Everyday Journal 4 | sidebar_label: Run App 5 | --- 6 | 7 | # Run App 8 | 9 | ``` 10 | npm start 11 | ``` 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/step-04-user-info.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-04-user-info 3 | title: Step 04 - Everyday Journal 4 | sidebar_label: User Information 5 | --- 6 | 7 | # User Information 8 | 9 | First we need to make sure every user has his/her own space. Let's get user information first. 10 | 11 | Import `Auth` 12 | ``` 13 | import { Auth } from 'aws-amplify'; 14 | ``` 15 | 16 | Get current user info 17 | ``` 18 | componentDidMount() { 19 | Auth.currentUserInfo() 20 | .then(user => this.setState({ user: user })) // we need user.id 21 | .catch(err => console.log(err)); 22 | } 23 | ``` 24 | -------------------------------------------------------------------------------- /website/docs/step-05-display-dates.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-05-display-dates 3 | title: Step 05 - List of Journals 4 | sidebar_label: Display Dates 5 | --- 6 | 7 | # Display Dates 8 | 9 | Convert to dropdown options 10 | ``` 11 | const history = dates.map(date => { 12 | return { 13 | key: date, 14 | value: date, 15 | text: date 16 | }; 17 | }); 18 | ``` 19 | 20 | Display 21 | ``` 22 | 28 | ``` 29 | -------------------------------------------------------------------------------- /website/docs/step-05-get-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-05-get-list 3 | title: Step 05 - List of Journals 4 | sidebar_label: Get List 5 | --- 6 | 7 | # Get List 8 | 9 | Over time user may record lots of daily happenings. That can be handled by a little bit more data structure. In this turorial we keep it simple, just list all memories and extract out dates from the list. 10 | 11 | Get list of everything 12 | ``` 13 | Storage.list(user.id) 14 | .then(data => { 15 | logger.debug('list of everything', data); 16 | this.extractDates(data); 17 | }) 18 | .catch(err => logger.error('error when get list of everything', err)); 19 | ``` 20 | 21 | Extract dates 22 | ``` 23 | extractDates(list) { 24 | const date_list = list.map(item => { 25 | const match = item.key.match(/\/(\d{4}-\d{2}-\d{2})\//); 26 | return match? match[1] : null; 27 | }); 28 | 29 | const unique_dates = Array.from(new Set(date_list)).sort().reverse(); 30 | this.setState({ dates: unique_dates }); 31 | } 32 | ``` 33 | -------------------------------------------------------------------------------- /website/docs/step-05-run-app.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-05-run-app 3 | title: Step 05 - List of Journals 4 | sidebar_label: Run App 5 | --- 6 | 7 | # Run App 8 | 9 | ``` 10 | npm start 11 | ``` 12 | 13 | 14 | -------------------------------------------------------------------------------- /website/docs/step-05-switch-date.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-05-switch-date 3 | title: Step 05 - List of Journals 4 | sidebar_label: Switch Date 5 | --- 6 | 7 | # Switch Date 8 | 9 | ``` 10 | handleDateChange(evt, data) { 11 | const date = data.value; 12 | const { user } = this.state; 13 | const path = user.id + '/' + date + '/'; 14 | 15 | this.setState({ 16 | date: date, 17 | path: path 18 | }); 19 | } 20 | ``` 21 | -------------------------------------------------------------------------------- /website/docs/step-06-build.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-06-build 3 | title: Step 06 - Go Live 4 | sidebar_label: Build 5 | --- 6 | 7 | ## Build 8 | 9 | ``` 10 | npm run build 11 | ``` 12 | 13 | The command generate everything needed into `build/` folder. 14 | -------------------------------------------------------------------------------- /website/docs/step-06-publish.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-06-publish 3 | title: Step 06 - Go Live 4 | sidebar_label: Publish 5 | --- 6 | 7 | # Publish 8 | 9 | This app is built completely in HTML/JS/CSS, no backend server needed. Just a static file hosting will do. Here we choose Amazon S3. 10 | 11 | In [Step 02](step-02-prepare.html) we created an AWS Mobile Hub project. During that step we have created everything that we needed for this app. 12 | 13 | Go to AWS Console -> Mobile Hub. Click `Resources` on the top-right corner. Look for "Amazon S3 Buckets", then find the bucket that has `hosting` inside its name. 14 | 15 | Upload files under `build/` folder to this bucket. Then open in browser. We are live! 16 | 17 | 18 | -------------------------------------------------------------------------------- /website/docs/step-06-touch-ups.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: step-06-touch-ups 3 | title: Step 06 - Go Live 4 | sidebar_label: Touch Ups 5 | --- 6 | 7 | # Touch Ups 8 | 9 | We need to change icon, app title. Also I've added a sidebar point to this GitHub repo. 10 | 11 | Not necessary to explain details in this step. Just check out the code should be fine. 12 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "scripts": { 3 | "examples": "dochameleon-examples", 4 | "start": "dochameleon-start", 5 | "build": "dochameleon-build", 6 | "publish-gh-pages": "dochameleon-publish" 7 | }, 8 | "devDependencies": { 9 | "dochameleon": "0.0.51" 10 | }, 11 | "dependencies": { 12 | "react": "^16.5.2" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /website/pages/README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 2 | 3 | # Journal 4 | Step by step tutorial to build a personal journal web app with ReactJS + AWS 5 | 6 | * [Step 01 - Create a Basic React App](docs/step-01-cra.html) 7 | * [Step 02 - Authentication](docs/step-02-prepare.html) 8 | * [Step 03 - Authentication UI](docs/step-03-replace-sign-in.html) 9 | * [Step 04 - Everyday Journal](docs/step-04-user-info.html) 10 | * [Step 05 - List of Journals](docs/step-05-get-list.html) 11 | * [Step 06 - Go Live](docs/step-06-touch-ups.html) 12 | 13 | Go to `journal` sub-folder of each step to check full source code, and run app. 14 | 15 | 16 | 17 | [Live Demo](http://journal-hosting-mobilehub-1908112296.s3-website-us-east-1.amazonaws.com/) 18 | 19 | **Disclaimer:** This is a personal experiment. Not an official sample. 20 | -------------------------------------------------------------------------------- /website/pages/index.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const path = require('path'); 3 | 4 | const dfs = require('../dfs.js'); 5 | 6 | const Doc = require('../components/docs/Doc.js'); 7 | 8 | const join = path.join; 9 | 10 | class ReadMe extends React.Component { 11 | render() { 12 | const { site, lang } = this.props; 13 | 14 | let readme_path = join(__dirname, './README.md'); 15 | const doc = site.docs.processMetadata(readme_path); 16 | return ( 17 |
18 | 19 | {doc.content} 20 | 21 |
22 | ); 23 | } 24 | } 25 | 26 | module.exports = ReadMe; 27 | -------------------------------------------------------------------------------- /website/pages/languages.js: -------------------------------------------------------------------------------- 1 | const React = require('react'); 2 | const { Row, Col } = require('fluid-react'); 3 | 4 | const MarkdownBlock = require('../components/MarkdownBlock.js'); 5 | 6 | const no_languages_content = ` 7 | To have multi-language, write translations under \`website/i18n/$lang_code$\` 8 | 9 | [Example](https://github.com/richardzcode/Dochameleon/tree/master/website/i18n) 10 | `; 11 | 12 | class Languages extends React.Component { 13 | render() { 14 | const { site, lang } = this.props; 15 | const { theme } = site; 16 | const langs = site.i18n.langs(); 17 | const languages = langs && langs.length > 0 18 | ? langs.map((language, i) => { 19 | return ( 20 | 21 | 22 | {site.i18n.translate(language, lang)} 23 | 24 | 25 | ); 26 | }) 27 | : {no_languages_content} 28 | 29 | return ( 30 |
31 |
32 | 33 | {languages} 34 | 35 |
36 |
37 | ); 38 | } 39 | } 40 | 41 | module.exports = Languages; 42 | -------------------------------------------------------------------------------- /website/siteConfig.js: -------------------------------------------------------------------------------- 1 | const currentYear = new Date().getFullYear(); 2 | 3 | const siteConfig = { 4 | projectName: 'Journal-AWS-Amplify-Tutorial', 5 | title: 'Journal Tutorial', 6 | tagline: 'Step by step tutorial to build a personal journal web app with ReactJS + AWS', 7 | copyright: 'Copyright © ' + currentYear + ' Richard Zhang', 8 | 9 | rootUrl: 'https://richardzcode.github.io', 10 | baseUrl: '/Journal-AWS-Amplify-Tutorial', 11 | 12 | icon: 'img/dochameleon.png', 13 | favicon: 'img/favicon.png', 14 | 15 | js: [ 16 | 'https://buttons.github.io/buttons.js' 17 | ], 18 | 19 | buildDir: '../docs' 20 | }; 21 | 22 | module.exports = siteConfig; 23 | -------------------------------------------------------------------------------- /website/static/img/f-r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/static/img/f-r.png -------------------------------------------------------------------------------- /website/static/img/fsts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/richardzcode/Journal-AWS-Amplify-Tutorial/4ba93b50ca45525d4691d808ff93bd2fd49efecb/website/static/img/fsts.png -------------------------------------------------------------------------------- /website/theme/custom.js: -------------------------------------------------------------------------------- 1 | const custom = { 2 | sideNavCategoryCap: { 3 | lineHeight: '1.2em', 4 | fontSize: '1em' 5 | }, 6 | sideNavItem: { 7 | padding: '2px 0' 8 | }, 9 | sideNavItemLink: { 10 | padding: '0', 11 | fontSize: '0.9em' 12 | }, 13 | prevnext: { 14 | fontSize: '0.8em' 15 | } 16 | }; 17 | 18 | module.exports = custom; 19 | -------------------------------------------------------------------------------- /website/theme/pages.js: -------------------------------------------------------------------------------- 1 | const color = require('./color.js'); 2 | 3 | const pages = { 4 | block: { 5 | padding: '30px 10px' 6 | }, 7 | blockEven: { 8 | padding: '30px 10px', 9 | background: '#e9e9e9' 10 | }, 11 | homeSplash: { 12 | padding: '2em 10px', 13 | textAlign: 'center' 14 | }, 15 | promoSection: { 16 | display: 'flex', 17 | flexFlow: 'row wrap', 18 | justifyContent: 'center', 19 | fontSize: '125%', 20 | lineHeight: '1.6em', 21 | position: 'relative', 22 | zIndex: '99' 23 | }, 24 | featureImageContainer: { 25 | textAlign: 'center' 26 | }, 27 | featureImage: { 28 | maxHeight: '80px' 29 | }, 30 | calloutTitle: { 31 | textAlign: 'left', 32 | textDecoration: 'none', 33 | color: color.title, 34 | fontWeight: '300', 35 | fontSize: '180%', 36 | lineHeight: '1em' 37 | }, 38 | calloutImageContainer: { 39 | textAlign: 'center' 40 | }, 41 | calloutImage: { 42 | maxWidth: '80%' 43 | }, 44 | showcaseBox: { 45 | display: 'block', 46 | padding: '20px', 47 | width: '80px', 48 | textAlign: 'center' 49 | }, 50 | showcaseImage: { 51 | maxWidth: '100%', 52 | maxHeight: '80px' 53 | }, 54 | helpTitle: { 55 | textAlign: 'left', 56 | color: color.title, 57 | fontWeight: '300', 58 | fontSize: '200%', 59 | lineHeight: '1em' 60 | }, 61 | helpSection: { 62 | padding: '20px' 63 | }, 64 | helpSectionTitle: { 65 | textAlign: 'left', 66 | color: color.title, 67 | fontWeight: '300', 68 | fontSize: '150%', 69 | lineHeight: '1em' 70 | } 71 | }; 72 | 73 | module.exports = pages; 74 | --------------------------------------------------------------------------------