Lorem ipsum, ...
23 |
24 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lesson-1/jquery/v1.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Lorem ipsum, ...
24 |
25 |
29 |
30 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-1/jquery/v2.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Lorem ipsum, ...
23 |
24 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/lesson-1/react/current/README.md:
--------------------------------------------------------------------------------
1 | Sample project with features similar to the `../jquery` sample project.
2 |
--------------------------------------------------------------------------------
/lesson-1/react/current/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "current",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.0.0",
7 | "react-dom": "^16.0.0",
8 | "react-scripts": "1.0.14"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test --env=jsdom",
14 | "eject": "react-scripts eject"
15 | }
16 | }
--------------------------------------------------------------------------------
/lesson-1/react/current/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-1/react/current/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-1/react/current/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ` with style class `Footer` and add it to App component.
36 |
37 | ### React State
38 | - State is similar to props, but it is private and fully controlled by the component
39 | - Class Components
40 | - Initial this.state is assigned in class constructor
41 | - Do not modify state directly, use setState()
42 |
43 | _Exercise (state) #4:_
44 | Display modal window to add a new note.
45 |
46 | _Exercise (state) #5:_
47 | Display or hide text in Note component.
48 |
49 | ### React Props
50 | - Props are attributes of component
51 | - Read-only - component never modify its own props
52 |
53 | _Exercise (props) #6:_
54 | Pass notes info via props from App to NoteList.
55 |
56 | _Exercise (props) #6:_
57 | Pass note info via props from NoteList to Note.
58 |
59 | _Exercise (props) #7:_
60 | Pass callback function from NewNoteModal to App and add a new note to a list.
61 |
62 | _EXTRA Exercise (props) #7:_
63 | Pass callback function from Note to App and remove note from a list.
64 |
65 | To run solution
66 | ```
67 | cd solution/
68 | npm i
69 | npm start # or PORT=3001 npm start to run in parallel with working app
70 | ```
71 |
72 | ## Useful resources
73 |
74 | - [React.js - Introducing JSX](https://facebook.github.io/react/docs/introducing-jsx.html)
75 | - [React.js - Rendering Elements](https://facebook.github.io/react/docs/rendering-elements.html)
76 | - [React.js - Components and props](https://facebook.github.io/react/docs/components-and-props.html)
77 | - [React.js - State and lifecycle](https://reactjs.org/docs/state-and-lifecycle.html)
78 | - [React Patterns](http://reactpatterns.com/)
79 | - [The many faces of `this` in javascript](https://blog.pragmatists.com/the-many-faces-of-this-in-javascript-5f8be40df52e)
--------------------------------------------------------------------------------
/lesson-2/idea-journal/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.0.0",
7 | "react-dom": "^16.0.0",
8 | "react-modal": "^3.0.3",
9 | "shortid": "^2.2.8"
10 | },
11 | "devDependencies": {
12 | "react-scripts": "1.0.14"
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 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-2/idea-journal/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-2/idea-journal/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.0.0",
7 | "react-dom": "^16.0.0",
8 | "react-modal": "^3.0.3",
9 | "shortid": "^2.2.8"
10 | },
11 | "devDependencies": {
12 | "react-scripts": "1.0.14"
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 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-2/idea-journal/solution/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './Header'
3 | import NoteList from './NoteList'
4 | import Footer from './Footer'
5 | import '../styles/App.css';
6 |
7 | class App extends React.Component {
8 | constructor() {
9 | super()
10 | this.state = {
11 | notes: [{
12 | title: 'MSD Code Academy',
13 | text: 'Let\'s crete React app with a few components.',
14 | uuid: 1
15 | }]
16 | }
17 | }
18 |
19 | addNoteToList = (note) => {
20 | const {notes} = this.state
21 | this.setState({
22 | notes: notes.concat(note)
23 | })
24 | }
25 |
26 | removeNoteFromList = (noteId) => () => {
27 | const {notes} = this.state
28 | const newNotes = notes.filter((note) => {
29 | return note.uuid !== noteId
30 | })
31 | this.setState({
32 | notes: newNotes
33 | })
34 | }
35 |
36 | render() {
37 | const {notes} = this.state
38 | return (
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | )
47 | }
48 | }
49 |
50 | export default App
51 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NewNoteModal from './NewNoteModal'
3 | import logo from '../logo.png'
4 | import '../styles/Header.css'
5 |
6 | class Header extends React.Component {
7 |
8 | render() {
9 | const {...props} = this.props
10 | return (
11 |
12 |
13 |

14 |
IDEA JOURNAL
15 |
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | export default Header
23 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import * as shortId from 'shortid'
4 | import '../styles/NewNoteModal.css'
5 |
6 | class NewNoteModal extends React.Component {
7 | constructor() {
8 | super()
9 | this.state = {
10 | modalIsOpen: false,
11 | note: {}
12 | }
13 | }
14 |
15 | toggleModal = () => {
16 | this.setState({
17 | modalIsOpen: !this.state.modalIsOpen
18 | })
19 | }
20 |
21 | handleChange = (field) => (e) => {
22 | let note = this.state.note
23 | note[field] = e.target.value
24 | this.setState({note})
25 | }
26 |
27 | handleFormSubmit = (e) => {
28 | e.preventDefault()
29 | const {onAddNote} = this.props
30 | const {note} = this.state
31 | onAddNote({...note, uuid: shortId.generate()})
32 | this.toggleModal()
33 | }
34 |
35 | render() {
36 | const {modalIsOpen} = this.state
37 | return (
38 |
39 |
ADD NOTE
40 |
45 | Add a new note
46 |
57 |
58 |
59 | )
60 | }
61 | }
62 |
63 | export default NewNoteModal
64 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Note.css'
3 |
4 | class Note extends React.Component {
5 |
6 | constructor() {
7 | super()
8 | this.state = {
9 | showMore: false
10 | }
11 | }
12 |
13 | handleShowMore = () => {
14 | this.setState({
15 | showMore: !this.state.showMore
16 | })
17 | }
18 |
19 | render() {
20 | const {noteId, title, text, removeNoteFromList} = this.props
21 | const {showMore} = this.state
22 | return (
23 |
31 | )
32 | }
33 | }
34 |
35 | export default Note
36 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/components/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const {notes, removeNoteFromList} = this.props
8 | return (
9 |
10 | {notes.map((note) => (
11 |
13 | ))}
14 |
15 | )
16 | }
17 | }
18 |
19 | export default NoteList
20 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import App from './components/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-2/idea-journal/solution/src/logo.png
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/solution/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './Header'
3 | import '../styles/App.css';
4 |
5 | class App extends React.Component {
6 | constructor() {
7 | super()
8 | this.state = {
9 | notes: [{
10 | title: 'MSD Code Academy',
11 | text: 'Let\'s crete React app with a few components.',
12 | uuid: 1
13 | }]
14 | }
15 | }
16 |
17 | addNoteToList = (note) => {
18 | // add new note, use Array.prototype.concat()
19 | }
20 |
21 | removeNoteFromList = (noteId) => () => {
22 | // filter out note based on noteId, use Array.prototype.filter()
23 | }
24 |
25 | render() {
26 | const {notes} = this.state
27 | return (
28 |
33 | )
34 | }
35 | }
36 |
37 | export default App
38 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NewNoteModal from './NewNoteModal'
3 | import logo from '../logo.png'
4 | import '../styles/Header.css'
5 |
6 | class Header extends React.Component {
7 |
8 | render() {
9 | const {...props} = this.props
10 | return (
11 |
12 |
13 |

14 |
IDEA JOURNAL
15 |
16 |
17 |
18 | )
19 | }
20 | }
21 |
22 | export default Header
23 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | // import * as shortId from 'shortid'
4 | import '../styles/NewNoteModal.css'
5 |
6 | class NewNoteModal extends React.Component {
7 | constructor() {
8 | super()
9 | this.state = {
10 | note: {}
11 | }
12 | }
13 |
14 | handleChange = (field) => (e) => {
15 | let note = this.state.note
16 | note[field] = e.target.value
17 | this.setState({note})
18 | }
19 |
20 | handleFormSubmit = (e) => {
21 | e.preventDefault()
22 | // add not to the list of notes and close modal window
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
ADD NOTE
29 |
34 | Add a new note
35 |
46 |
47 |
48 | )
49 | }
50 | }
51 |
52 | export default NewNoteModal
53 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/components/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/NoteList.css'
3 |
4 | class NoteList extends React.Component {
5 | render() {
6 | return (
7 | 'NoteList'
8 | )
9 | }
10 | }
11 |
12 | export default NoteList
13 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import App from './components/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-2/idea-journal/src/logo.png
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-2/idea-journal/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-3/README.md:
--------------------------------------------------------------------------------
1 | # React Testing
2 |
3 | ## The Goal
4 |
5 | Learn about different types of automated tests and how to apply them in React environment.
6 |
7 | ## Automated tests
8 |
9 | 
10 |
11 | [The test pyramid](https://martinfowler.com/bliki/TestPyramid.html)
12 |
13 | - **End To End tests**: Slow, complicated, brittle, complete
14 | - **Unit tests**: Fast, simple, reliable, isolated
15 |
16 | ## How to write a good test
17 |
18 | 1. Write a failing test
19 | 2. Observe that it actually fails!
20 | 3. Make sure that it fails with red
21 | 4. Fix code so that the test passes
22 | 5. GOTO 1
23 |
24 | ## How to write a code that is easy to test
25 |
26 | **Pure functions** for the win:
27 | - Given an input, always produces the same output
28 | - No side effects (including changing its arguments)
29 |
30 | React Components are usually pure functions. Not a coincidence!
31 |
32 | ## Tools that we will use
33 |
34 | - [Jest - Delightful JavaScript Testing](https://facebook.github.io/jest/) framework and toolset for testing
35 | - [Enzyme](https://github.com/airbnb/enzyme) library for testing React components
36 |
37 | ## Tools that we will not use
38 |
39 | **E2E testing**: Selenium, Webdrivers, Headless browsers, Robot Framework (because they take too much time to set up and learn)
40 |
41 | **Unit testing**: Mocha, Chai, Expect, Istanbul, Sinon (because these are alternatives to Jest)
42 |
43 | **Server/API testing**: Supertest (because we focus on frontend only, for now)
44 |
45 | ## Hands on!
46 |
47 | _Excercise 1: Checkout lesson-3 folder and run the app! `npm install`, `npm start`_
48 |
49 | _Excercise 2: Find your favourite bug_
50 |
51 | _Excercise 3: Write a unit test and `npm test`_
52 |
53 | https://facebook.github.io/jest/docs/en/expect.html
54 |
55 | Hint:
56 | ```javascript
57 | import { removeFromArray } from "./functions"
58 |
59 | // test suite: organize your tests!
60 | describe("functions.test.js: removeFromArray", () => {
61 |
62 | // single test
63 | it("should remove item from array", () => {
64 | const input = ... // prepare data
65 | const actual = ... // call the function here
66 | const expected = ... // what you want to see?
67 | expect(actual).toEqual(expected) // test!
68 | })
69 | })
70 | ```
71 |
72 | _Excercise 4: Write a Component test using enzyme_
73 |
74 | http://airbnb.io/enzyme/docs/api/shallow.html
75 |
76 | https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#testing-components
77 |
78 | ```javascript
79 | import React from "react" // because we will use JSX
80 | import Party from "./Party"
81 | import { shallow } from "enzyme" // there are multiple kinds of rendering
82 |
83 | describe("Party.js", () => {
84 |
85 | it("should display party name", () => {
86 | const party = { name: "MyParty", members: 100 } // some data that we pass to props
87 | const wrapper = shallow(
)
88 | const text = wrapper.text()
89 | expect(text).toMatch("MyParty")
90 | })
91 |
92 | })
93 | ```
94 |
95 | _Excercise 5: Write propTypes_
96 |
97 | Technically, this is not a test. But it will help you!
98 |
99 | https://reactjs.org/docs/typechecking-with-proptypes.html
100 |
101 |
102 |
103 | ## Reading and more links
104 |
105 | (You may think I am biased towards Eric Elliot - perhaps. But he does write well!)
106 |
107 | - [5 Questions Every Unit Test Must Answer](https://medium.com/javascript-scene/what-every-unit-test-needs-f6cd34d9836d)
108 | - [5 Common Misconceptions About TDD & Unit Tests](https://medium.com/javascript-scene/5-common-misconceptions-about-tdd-unit-tests-863d5beb3ce9)
109 | - [TDD the RITE Way](https://medium.com/javascript-scene/tdd-the-rite-way-53c9b46f45e3)
110 | - [Learn Test Driven Development (TDD)](https://github.com/dwyl/learn-tdd)
111 | - https://github.com/msd-code-academy/03-testing-react-app
112 | - [What is a Pure Function?](https://medium.com/javascript-scene/master-the-javascript-interview-what-is-a-pure-function-d1c076bec976)
113 | - [Pure Happiness with Pure Functions](https://drboolean.gitbooks.io/mostly-adequate-guide/content/ch3.html)
114 |
--------------------------------------------------------------------------------
/lesson-3/coalition/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org/
2 |
3 | # top-most EditorConfig file
4 | root = true
5 |
6 | # Unix-style newlines with a newline ending every file
7 | end_of_line = lf
8 | insert_final_newline = true
9 |
10 | # UTF-8 as default charset
11 | charset = utf-8
12 |
13 | # Consistent indentation
14 | indent_size = 2
15 | indent_style = space
16 |
--------------------------------------------------------------------------------
/lesson-3/coalition/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "coalition",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.0.0",
7 | "react-dom": "^16.0.0",
8 | "react-scripts": "1.0.14"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test --env=jsdom",
14 | "eject": "react-scripts eject"
15 | },
16 | "devDependencies": {
17 | "enzyme": "^3.1.0",
18 | "enzyme-adapter-react-16": "^1.0.2",
19 | "react-test-renderer": "^16.0.0"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/lesson-3/coalition/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-3/coalition/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-3/coalition/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-3/coalition/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 60px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | @keyframes App-logo-spin {
26 | from { transform: rotate(0deg); }
27 | to { transform: rotate(360deg); }
28 | }
29 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import './App.css';
4 | import parties from "./election-results"
5 | import PartiesList from "./PartiesList"
6 | import Coalition from "./Coalition"
7 |
8 | import { removeFromArray } from "./functions"
9 | class App extends Component {
10 |
11 | constructor(props) {
12 | super(props)
13 | this.state = {
14 | coalition: []
15 | }
16 | }
17 |
18 | addParty = (party) => {
19 | this.setState({
20 | coalition: this.state.coalition.concat(party)
21 | })
22 | }
23 |
24 |
25 | removeParty = (partyToRemove) => {
26 | this.setState({
27 | coalition: removeFromArray(this.state.coalition, partyToRemove)
28 | })
29 | }
30 |
31 | render() {
32 | return (
33 |
34 |
35 |
36 | Welcome to Czech Republic, 2017
37 | Build your own coalition!
38 |
39 |
40 |
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | export default App;
48 |
--------------------------------------------------------------------------------
/lesson-3/coalition/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 | });
9 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/Coalition.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import "./PartiesList.css"
3 |
4 | import Party from "./Party"
5 |
6 | import { sumMembers } from "./functions"
7 |
8 | export default class Coalition extends Component {
9 | render() {
10 |
11 | const members = sumMembers(this.props.parties)
12 |
13 | return (
14 |
15 |
16 | Your Coalition has {members} members.
17 |
18 |
19 | {this.props.parties.map(party =>
20 |
21 | )}
22 |
23 |
24 | )
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/PartiesList.css:
--------------------------------------------------------------------------------
1 | .PartiesList-wrapper {
2 | display: flex;
3 | flex-wrap: wrap;
4 | }
5 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/PartiesList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react"
2 | import "./PartiesList.css"
3 |
4 | import Party from "./Party"
5 |
6 | export default class PartiesList extends Component {
7 | render() {
8 | return (
9 |
10 | {this.props.parties.map(party =>
11 |
12 | )}
13 |
14 | )
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/Party.css:
--------------------------------------------------------------------------------
1 | .Party {
2 | border: 3px solid #ddd;
3 | border-left-width: 15px;
4 | border-right-width: 15px;
5 | border-radius: 3px;
6 | width: 20em;
7 | margin: 5px;
8 | flex: 0 1 auto;
9 | }
10 |
11 | .Party:hover {
12 | cursor: pointer;
13 | box-shadow: 0 0 10px 0 gray;
14 | }
15 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/Party.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react"
2 | import "./Party.css"
3 |
4 | export default class Party extends PureComponent {
5 |
6 | selectParty = (e) => {
7 | this.props.partySelected(this.props.party)
8 | }
9 | render() {
10 | const { party } = this.props
11 | return (
12 |
13 | {party.name}
14 |
15 | Members in parliament: {party.name}
16 |
17 |
18 | )
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/election-results.json:
--------------------------------------------------------------------------------
1 | [{
2 | "name": "Dynamic Fever",
3 | "members": 78,
4 | "color": "#261060"
5 | }, {
6 | "name": "Twins of Valor",
7 | "members": 25,
8 | "color": "#004494"
9 | }, {
10 | "name": "Groove of Rhythm",
11 | "members": 22,
12 | "color": "#000"
13 | }, {
14 | "name": "Attic Airwaves",
15 | "members": 22,
16 | "color": "#2C86C4"
17 | }, {
18 | "name": "Sense of the New Sound",
19 | "members": 15,
20 | "color": "#D8261C"
21 | }, {
22 | "name": "Pajama Critters",
23 | "members": 15,
24 | "color": "#f29400"
25 | }, {
26 | "name": "Standby Brigade",
27 | "members": 10,
28 | "color": "#F9DA23"
29 | }, {
30 | "name": "Feedback of Mayhem",
31 | "members": 7,
32 | "color": "#939"
33 | }, {
34 | "name": "Iron Birds",
35 | "members": 6,
36 | "color": "#444E57"
37 | }, {
38 | "name": "Diary Of The Atomic Skillet",
39 | "members": 0,
40 | "color": "#090"
41 | }]
42 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/functions.js:
--------------------------------------------------------------------------------
1 | export function removeFromArray(haystack, needle) {
2 | return haystack.filter(item => item === needle)
3 | }
4 |
5 | export function sumMembers(allParties) {
6 | return allParties
7 | .map(party => party.members)
8 | .reduce((a, b) => a + b, 0)
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lesson-3/coalition/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // http://airbnb.io/enzyme/docs/installation/index.html#working-with-react-16
2 | import { configure } from 'enzyme'
3 | import Adapter from 'enzyme-adapter-react-16'
4 | configure({ adapter: new Adapter() })
5 |
--------------------------------------------------------------------------------
/lesson-3/inspiration/Party.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react"
2 | import PropTypes from "prop-types"
3 | import "./Party.css"
4 |
5 | export default class Party extends PureComponent {
6 |
7 | selectParty = (e) => {
8 | this.props.partySelected(this.props.party)
9 | }
10 | render() {
11 | const { party } = this.props
12 | return (
13 |
14 | {party.name}
15 |
16 | Members in parliament: {party.name}
17 |
18 |
19 | )
20 | }
21 | }
22 |
23 | Party.propTypes = {
24 | }
25 |
26 | Party.defaultProps = {
27 | }
28 |
--------------------------------------------------------------------------------
/lesson-3/inspiration/Party.test.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Party from "./Party"
3 | import { shallow } from "enzyme"
4 |
5 | describe("Party.js", () => {
6 | it("should render", () => {
7 | shallow(
)
8 | })
9 |
10 | it("should display party name")
11 |
12 | it("should display amount of members")
13 |
14 | it("should match snapshot")
15 |
16 | })
17 |
--------------------------------------------------------------------------------
/lesson-3/inspiration/functions.test.js:
--------------------------------------------------------------------------------
1 | import { removeFromArray } from "./functions"
2 |
3 | describe("functions.test.js removeFromArray", () => {
4 | it.skip("should remove item from array", () => {
5 | // TODO implement test here and remove `.skip`
6 | })
7 |
8 | // Tests without callback function are automatically pending (as an alternative to .skip)
9 | it("should work with empty array")
10 |
11 | it("should not remove anything if needle does not exist")
12 |
13 | it("should not modify the parameter passed (immutable)")
14 |
15 | it("should throw Errors when passed non-Array")
16 | })
17 |
--------------------------------------------------------------------------------
/lesson-3/solution/Party.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from "react"
2 | import PropTypes from "prop-types"
3 | import "./Party.css"
4 |
5 | export default class Party extends PureComponent {
6 |
7 | selectParty = (e) => {
8 | this.props.partySelected(this.props.party)
9 | }
10 | render() {
11 | const { party } = this.props
12 | return (
13 |
14 | {party.name}
15 |
16 | Members in parliament: {party.name}
17 |
18 |
19 | )
20 | }
21 | }
22 |
23 | Party.propTypes = {
24 | party: PropTypes.shape({
25 | name: PropTypes.string.isRequired,
26 | members: PropTypes.number.isRequired,
27 | color: PropTypes.string
28 | }),
29 | partySelected: PropTypes.func
30 | }
31 |
32 | Party.defaultProps = {
33 | partySelected: () => {
34 | /* Do nothing */
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lesson-3/solution/Party.test.js:
--------------------------------------------------------------------------------
1 | import React from "react"
2 | import Party from "./Party"
3 | import { shallow } from "enzyme"
4 |
5 | describe("Party.js", () => {
6 | it("should render", () => {
7 | shallow(
)
8 | })
9 |
10 | it("should display party name", () => {
11 | const party = { name: "MyParty", members: 100 }
12 | const wrapper = shallow(
)
13 | const text = wrapper.text()
14 | expect(text).toMatch("MyParty")
15 | })
16 |
17 | it("should display amount of members", () => {
18 | const party = { name: "MyParty", members: 100 }
19 | const wrapper = shallow(
)
20 | const text = wrapper.text()
21 | expect(text).toMatch("100")
22 | })
23 |
24 | it("should match snapshot", () => {
25 | const party = { name: "MyParty", members: 100 }
26 | const wrapper = shallow(
)
27 | const component = wrapper.debug()
28 | expect(component).toMatchSnapshot()
29 | })
30 |
31 | })
32 |
--------------------------------------------------------------------------------
/lesson-3/solution/functions.test.js:
--------------------------------------------------------------------------------
1 | import { removeFromArray } from "./functions"
2 |
3 | describe("functions.test.js removeFromArray", () => {
4 | it("should remove item from array", () => {
5 | const arr = [1, 2, 3, 4]
6 | const expected = [1, 2, 4]
7 | const actual = removeFromArray(arr, 3)
8 | expect(actual).toEqual(expected)
9 | })
10 |
11 | it("should work with empty array", () => {
12 | const actual = removeFromArray([], 1)
13 | expect(actual).toEqual([])
14 | })
15 |
16 | it("should not remove anything if needle does not exist", () => {
17 | const arr = [1, 2, 3, 4]
18 | const actual = removeFromArray(arr, "Karel")
19 | const expected = [1, 2, 3, 4]
20 | expect(actual).toEqual(expected)
21 | })
22 |
23 | it("should not modify the parameter passed (immutable)", () => {
24 | const arr = [1, 2]
25 | const actual = removeFromArray(arr, 1)
26 | expect(arr).toEqual([1, 2]) // the original array is not modified
27 | })
28 |
29 | it("should throw Errors when passed non-Array", () => {
30 | expect(() => removeFromArray(undefined, null))
31 | .toThrow()
32 | })
33 | })
34 |
--------------------------------------------------------------------------------
/lesson-4/.editorconfig:
--------------------------------------------------------------------------------
1 | # EditorConfig helps developers define and maintain
2 | # consistent coding styles between different editors and IDEs.
3 |
4 | root = true
5 |
6 | [*]
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 | indent_style = space
12 | indent_size = 2
13 |
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/lesson-4/README.md:
--------------------------------------------------------------------------------
1 | # Advanced React - Async
2 |
3 | ## Goal - understand (unidirectional) data flow
4 |
5 | ## TODO
6 |
7 | * [recap] Component's life cycle hooks recap
8 | * [recap] State & Props
9 | * [recap] Children + [key](https://stackoverflow.com/questions/28329382/understanding-unique-keys-for-array-children-in-react-js#28329550) (re-render)
10 | * [recap] PropTypes - Runtime TypeSafety
11 | * [recap] Promises + Async
12 | * [concept] HOC - Higher order components
13 | * [concept] FAC - Function as children / [Render callback](http://reactpatterns.com/#render-callback)
14 | * [new] Effects
15 | * [new] Local (Components) state vs. Centralized (Atom) state
16 | * [new] Error handling / Loading (the state machine)
17 |
18 | ## Problem - "Idea Journal"
19 |
20 | * make it work with server - CRUD
21 | * display loading state indicator
22 | * validate server payload via propTypes
23 |
24 | ## Exercises
25 |
26 | 1. display total number of notes in header // passing state down
27 | 1. prevent saving an empty note (button must become disabled)
28 | 1. add propTypes for `Header.js` and `Note.js` components
29 | 1. replace the `default` with your github username in `./src/config/api.js`
30 | 1. run `node upload-data.js`
31 |
32 | 1. implement "load notes from server" (will be step-by-step walk through) 😈
33 | 1. fetch notes in `componentDidMount` of App.js, transform and save notes to state
34 | 1. in App.js display `
` instead of `
` during server call
35 | 1. in App.js display 0 notes if there are no notes on server
36 | 1. in App.js display an `errorMessage`
37 | 1. implement
38 | 1. implement note deletion
39 | 1. add propTypes everywhere
40 |
41 | ## Learning resources
42 |
43 | * [React Forms](https://reactjs.org/docs/forms.html)
44 | * [You Might Need Redux](http://redux.js.org/)
45 | * [You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367)
46 | * [Michael Jackson - Never Write Another HoC](https://www.youtube.com/watch?v=BcVAq3YFiuc)
47 | * [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API)
48 |
--------------------------------------------------------------------------------
/lesson-4/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "15.6.0",
7 | "react": "16.0.0",
8 | "react-dom": "16.0.0",
9 | "react-modal": "3.0.3",
10 | "request": "2.83.0",
11 | "shortid": "2.2.8"
12 | },
13 | "devDependencies": {
14 | "prettier": "1.7.4",
15 | "react-scripts": "1.0.14"
16 | },
17 | "scripts": {
18 | "start": "react-scripts start",
19 | "build": "react-scripts build",
20 | "test": "react-scripts test --env=jsdom",
21 | "eject": "react-scripts eject"
22 | },
23 | "prettier": {
24 | "printWidth": 100,
25 | "tabWidth": 2,
26 | "useTabs": false,
27 | "semi": false,
28 | "bracketSpacing": true,
29 | "jsxBracketSameLine": true,
30 | "singleQuote": true,
31 | "trailingComma": "es5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lesson-4/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-4/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-4/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-4/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './Header'
3 | import NoteList from './NoteList'
4 | import Footer from './Footer'
5 | import Spinner from './Spinner'
6 |
7 | import { FIREBASE_URL } from '../config/api'
8 | import '../styles/App.css'
9 |
10 | class App extends React.Component {
11 | state = {
12 | notes: [],
13 | errorMessage: '',
14 | }
15 |
16 | async fetchNotes() {
17 | try {
18 | const result = await fetch(`${FIREBASE_URL}/notes.json`)
19 | const notes = await result.json()
20 |
21 | this.setState({
22 | notes: Object.keys(notes).map(key => ({
23 | uuid: key,
24 | ...notes[key],
25 | })),
26 | })
27 | } catch (err) {
28 | this.setState({
29 | errorMessage: err.message ? err.message : err,
30 | })
31 | }
32 | }
33 |
34 | componentDidMount() {
35 | this.fetchNotes()
36 | }
37 |
38 | addNoteToList = note => {
39 | this.setState({
40 | notes: [...this.state.notes, note],
41 | })
42 | }
43 |
44 | removeNoteFromList = noteId => () => {
45 | const { notes } = this.state
46 | const newNotes = notes.filter(note => {
47 | return note.uuid !== noteId
48 | })
49 | this.setState({
50 | notes: newNotes,
51 | })
52 | }
53 |
54 | editNote = async note => {
55 | try {
56 | const result = await fetch(`${FIREBASE_URL}/notes/${note.noteId}.json`, {
57 | method: 'put',
58 | headers: {
59 | Accept: 'application/json',
60 | 'Content-Type': 'application/json',
61 | },
62 | body: JSON.stringify({
63 | title: note.title,
64 | text: note.text,
65 | }),
66 | })
67 |
68 | if (result.ok) {
69 | this.fetchNotes()
70 | }
71 | } catch (err) {
72 | console.error('editNote: ',err)
73 | this.setState({
74 | errorMessage: `Error during note update`,
75 | })
76 | }
77 | }
78 |
79 | render() {
80 | const { errorMessage, notes } = this.state
81 | return (
82 |
83 |
84 |
85 | {errorMessage}
86 | {notes.length === 0 ? (
87 |
88 | ) : (
89 |
94 | )}
95 |
96 |
97 |
98 | )
99 | }
100 | }
101 |
102 | export default App
103 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/EditNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import '../styles/EditNoteModal.css'
4 |
5 | class EditNoteModal extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | modalIsOpen: false,
10 | note: {
11 | noteId: props.noteId,
12 | text: props.text,
13 | title: props.title,
14 | },
15 | }
16 | }
17 |
18 | toggleModal = () => {
19 | this.setState({
20 | modalIsOpen: !this.state.modalIsOpen,
21 | })
22 | }
23 |
24 | handleChange = field => e => {
25 | let note = this.state.note
26 | note[field] = e.target.value
27 | this.setState({ note })
28 | }
29 |
30 | handleFormSubmit = e => {
31 | e.preventDefault()
32 | this.props.editNote(this.state.note)
33 | this.toggleModal()
34 | }
35 |
36 | render() {
37 | const { modalIsOpen } = this.state
38 | return (
39 |
40 |
edit
41 |
47 | Edit a note
48 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default EditNoteModal
76 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import NewNoteModal from './NewNoteModal'
5 | import logo from '../logo.png'
6 | import '../styles/Header.css'
7 |
8 | class Header extends React.Component {
9 | render() {
10 | const { ...props } = this.props
11 | return (
12 |
13 |
14 |

15 |
IDEA JOURNAL
16 |
(containing {this.props.noteCount} ideas)
17 |
18 |
19 |
20 | )
21 | }
22 | }
23 |
24 | Header.propTypes = {
25 | noteCount: PropTypes.number.isRequired
26 | }
27 |
28 | export default Header
29 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import * as shortId from 'shortid'
4 | import '../styles/NewNoteModal.css'
5 |
6 | class NewNoteModal extends React.Component {
7 | constructor() {
8 | super()
9 | this.state = {
10 | modalIsOpen: false,
11 | note: {},
12 | }
13 | }
14 |
15 | toggleModal = () => {
16 | this.setState({
17 | modalIsOpen: !this.state.modalIsOpen,
18 | })
19 | }
20 |
21 | handleChange = field => e => {
22 | let note = this.state.note
23 | note[field] = e.target.value
24 | this.setState({ note })
25 | }
26 |
27 | handleFormSubmit = e => {
28 | e.preventDefault()
29 | const { onAddNote } = this.props
30 | const { note } = this.state
31 | onAddNote({ ...note, uuid: shortId.generate() })
32 | this.toggleModal()
33 | }
34 |
35 | render() {
36 | const { modalIsOpen } = this.state
37 | return (
38 |
39 |
ADD NOTE
40 |
46 | Add a new note
47 |
58 |
59 |
60 | )
61 | }
62 | }
63 |
64 | export default NewNoteModal
65 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import PropTypes from 'prop-types'
3 |
4 | import EditNoteModal from './EditNoteModal'
5 | import '../styles/Note.css'
6 |
7 | class Note extends React.Component {
8 | state = {
9 | showMore: false,
10 | }
11 |
12 | handleShowMore = () => {
13 | this.setState({
14 | showMore: !this.state.showMore,
15 | })
16 | }
17 |
18 | render() {
19 | const { noteId, title, text, removeNoteFromList } = this.props
20 | const { showMore } = this.state
21 | return (
22 |
33 | )
34 | }
35 | }
36 |
37 | Note.propTypes = {
38 | noteId: PropTypes.string.isRequired,
39 | title: PropTypes.string.isRequired,
40 | text: PropTypes.string.isRequired,
41 | removeNoteFromList: PropTypes.func.isRequired,
42 | editNote: PropTypes.func.isRequired
43 | }
44 |
45 | export default Note
46 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const { notes, editNote, removeNoteFromList } = this.props
8 | return (
9 |
10 | {notes.map(note => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default NoteList
26 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Spinner.css'
3 |
4 | // http://tobiasahlin.com/spinkit/
5 | const Spinner = () => (
6 |
11 | )
12 |
13 | export default Spinner
14 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/config/api.js:
--------------------------------------------------------------------------------
1 | const GITHUB_USERNAME = 'default'
2 |
3 | module.exports = {
4 | FIREBASE_URL: `https://msd-code-academy-1.firebaseio.com/${GITHUB_USERNAME}`,
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import App from './components/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-4/src-solution/logo.png
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/EditNoteModal.css:
--------------------------------------------------------------------------------
1 | .EditNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .EditNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .EditNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .EditNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .EditNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .EditNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .EditNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .EditNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .EditNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .EditNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .EditNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .EditNoteModal-modal-window input[type="text"],
76 | .EditNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
14 |
15 | .Header-logo span {
16 | margin: 10px
17 | }
18 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/Spinner.css:
--------------------------------------------------------------------------------
1 | .spinner {
2 | margin: 100px auto 0;
3 | width: 70px;
4 | text-align: center;
5 | }
6 |
7 | .spinner > div {
8 | width: 18px;
9 | height: 18px;
10 | background-color: #333;
11 |
12 | border-radius: 100%;
13 | display: inline-block;
14 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
15 | animation: sk-bouncedelay 1.4s infinite ease-in-out both;
16 | }
17 |
18 | .spinner .bounce1 {
19 | -webkit-animation-delay: -0.32s;
20 | animation-delay: -0.32s;
21 | }
22 |
23 | .spinner .bounce2 {
24 | -webkit-animation-delay: -0.16s;
25 | animation-delay: -0.16s;
26 | }
27 |
28 | @-webkit-keyframes sk-bouncedelay {
29 | 0%, 80%, 100% { -webkit-transform: scale(0) }
30 | 40% { -webkit-transform: scale(1.0) }
31 | }
32 |
33 | @keyframes sk-bouncedelay {
34 | 0%, 80%, 100% {
35 | -webkit-transform: scale(0);
36 | transform: scale(0);
37 | } 40% {
38 | -webkit-transform: scale(1.0);
39 | transform: scale(1.0);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lesson-4/src-solution/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Header from './Header'
3 | import NoteList from './NoteList'
4 | import Footer from './Footer'
5 |
6 | // TODO: use this url to work with the API
7 | // import { FIREBASE_URL } from '../config/api'
8 | import '../styles/App.css'
9 |
10 | class App extends React.Component {
11 | state = {
12 | notes: [
13 | {
14 | title: 'MSD Code Academy',
15 | text: "Let's crete React app with a few components.",
16 | uuid: 1,
17 | },
18 | ],
19 | errorMessage: '',
20 | }
21 |
22 | addNoteToList = note => {
23 | this.setState({
24 | notes: [...this.state.notes, note],
25 | })
26 | }
27 |
28 | removeNoteFromList = noteId => () => {
29 | const { notes } = this.state
30 | const newNotes = notes.filter(note => {
31 | return note.uuid !== noteId
32 | })
33 | this.setState({
34 | notes: newNotes,
35 | })
36 | }
37 |
38 | render() {
39 | const { errorMessage, notes } = this.state
40 | return (
41 |
42 |
43 | {/* TODO1: pass `noteCount` to Header */}
44 |
45 | {/* TODO3: display `errorMessage` */}
46 | {/* TODO2: display spinner when 0 notes or server call in progress */}
47 | {/* TODO4: optionally add logic to display message with 0 notes, or Spinner */}
48 |
49 |
50 |
51 |
52 | )
53 | }
54 | }
55 |
56 | export default App
57 |
--------------------------------------------------------------------------------
/lesson-4/src/components/EditNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import '../styles/EditNoteModal.css'
4 |
5 | class EditNoteModal extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | modalIsOpen: false,
10 | note: {
11 | noteId: props.noteId,
12 | text: props.text,
13 | title: props.title,
14 | },
15 | }
16 | }
17 |
18 | toggleModal = () => {
19 | this.setState({
20 | modalIsOpen: !this.state.modalIsOpen,
21 | })
22 | }
23 |
24 | handleChange = field => e => {
25 | let note = this.state.note
26 | note[field] = e.target.value
27 | this.setState({ note })
28 | }
29 |
30 | handleFormSubmit = e => {
31 | e.preventDefault()
32 | this.props.editNote(this.state.note)
33 | this.toggleModal()
34 | }
35 |
36 | render() {
37 | const { modalIsOpen } = this.state
38 | return (
39 |
40 |
edit
41 |
47 | Edit a note
48 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default EditNoteModal
76 |
--------------------------------------------------------------------------------
/lesson-4/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-4/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import NewNoteModal from './NewNoteModal'
4 | import logo from '../logo.png'
5 | import '../styles/Header.css'
6 |
7 | class Header extends React.Component {
8 | render() {
9 | const { ...props } = this.props
10 | return (
11 |
12 |
13 |

14 |
IDEA JOURNAL
15 | {/* TODO: replace ??? with `noteCount` prop, make sure it's passed from App.js */}
16 |
(containing ??? ideas)
17 |
18 |
19 |
20 | )
21 | }
22 | }
23 |
24 | export default Header
25 |
--------------------------------------------------------------------------------
/lesson-4/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import * as shortId from 'shortid'
4 | import '../styles/NewNoteModal.css'
5 |
6 | class NewNoteModal extends React.Component {
7 | constructor() {
8 | super()
9 | this.state = {
10 | modalIsOpen: false,
11 | note: {},
12 | }
13 | }
14 |
15 | toggleModal = () => {
16 | this.setState({
17 | modalIsOpen: !this.state.modalIsOpen,
18 | })
19 | }
20 |
21 | handleChange = field => e => {
22 | let note = this.state.note
23 | note[field] = e.target.value
24 | this.setState({ note })
25 | }
26 |
27 | handleFormSubmit = e => {
28 | e.preventDefault()
29 | const { onAddNote } = this.props
30 | const { note } = this.state
31 | onAddNote({ ...note, uuid: shortId.generate() })
32 | this.toggleModal()
33 | }
34 |
35 | render() {
36 | const { modalIsOpen } = this.state
37 | return (
38 |
39 |
ADD NOTE
40 |
46 | Add a new note
47 |
58 |
59 |
60 | )
61 | }
62 | }
63 |
64 | export default NewNoteModal
65 |
--------------------------------------------------------------------------------
/lesson-4/src/components/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import EditNoteModal from './EditNoteModal'
4 | import '../styles/Note.css'
5 |
6 | class Note extends React.Component {
7 | state = {
8 | showMore: false,
9 | }
10 |
11 | handleShowMore = () => {
12 | this.setState({
13 | showMore: !this.state.showMore,
14 | })
15 | }
16 |
17 | render() {
18 | const { noteId, title, text, removeNoteFromList } = this.props
19 | const { showMore } = this.state
20 | return (
21 |
32 | )
33 | }
34 | }
35 |
36 | export default Note
37 |
--------------------------------------------------------------------------------
/lesson-4/src/components/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const { notes, removeNoteFromList } = this.props
8 | return (
9 |
10 | {notes.map(note => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default NoteList
26 |
--------------------------------------------------------------------------------
/lesson-4/src/components/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Spinner.css'
3 |
4 | // http://tobiasahlin.com/spinkit/
5 | const Spinner = () => (
6 |
11 | )
12 |
13 | export default Spinner
14 |
--------------------------------------------------------------------------------
/lesson-4/src/config/api.js:
--------------------------------------------------------------------------------
1 | const GITHUB_USERNAME = 'default'
2 |
3 | module.exports = {
4 | FIREBASE_URL: `https://msd-code-academy-1.firebaseio.com/${GITHUB_USERNAME}`,
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './styles/index.css';
4 | import App from './components/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(
, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/lesson-4/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-4/src/logo.png
--------------------------------------------------------------------------------
/lesson-4/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/EditNoteModal.css:
--------------------------------------------------------------------------------
1 | .EditNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .EditNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .EditNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .EditNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .EditNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .EditNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .EditNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .EditNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .EditNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .EditNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .EditNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .EditNoteModal-modal-window input[type="text"],
76 | .EditNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
14 |
15 | .Header-logo span {
16 | margin: 10px
17 | }
18 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/Spinner.css:
--------------------------------------------------------------------------------
1 | /* This code is pasted from http://tobiasahlin.com/spinkit/ */
2 |
3 | .spinner {
4 | margin: 100px auto 0;
5 | width: 70px;
6 | text-align: center;
7 | }
8 |
9 | .spinner > div {
10 | width: 18px;
11 | height: 18px;
12 | background-color: #333;
13 |
14 | border-radius: 100%;
15 | display: inline-block;
16 | -webkit-animation: sk-bouncedelay 1.4s infinite ease-in-out both;
17 | animation: sk-bouncedelay 1.4s infinite ease-in-out both;
18 | }
19 |
20 | .spinner .bounce1 {
21 | -webkit-animation-delay: -0.32s;
22 | animation-delay: -0.32s;
23 | }
24 |
25 | .spinner .bounce2 {
26 | -webkit-animation-delay: -0.16s;
27 | animation-delay: -0.16s;
28 | }
29 |
30 | @-webkit-keyframes sk-bouncedelay {
31 | 0%, 80%, 100% { -webkit-transform: scale(0) }
32 | 40% { -webkit-transform: scale(1.0) }
33 | }
34 |
35 | @keyframes sk-bouncedelay {
36 | 0%, 80%, 100% {
37 | -webkit-transform: scale(0);
38 | transform: scale(0);
39 | } 40% {
40 | -webkit-transform: scale(1.0);
41 | transform: scale(1.0);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/lesson-4/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-4/upload-data.js:
--------------------------------------------------------------------------------
1 | // upload sample data to backend
2 |
3 | const request = require('request')
4 | const FIREBASE_URL = require('./src/config/api').FIREBASE_URL
5 |
6 | const notes = [
7 | {
8 | title: 'Day 1',
9 | text: 'Today is the first day of our training',
10 | },
11 |
12 | {
13 | title: 'Day 2',
14 | text: 'We started programming in JavaScript',
15 | },
16 |
17 | {
18 | title: 'Day 3 - JavaScript is awesome',
19 | text: 'Today we created our first React App - JavaScript is awesome!!!',
20 | },
21 | ]
22 |
23 | request(
24 | {
25 | method: 'PUT',
26 | uri: `${FIREBASE_URL}.json`,
27 | body: JSON.stringify({}),
28 | },
29 | (delErr, delResponse, delBody) => {
30 | if (delErr) {
31 | console.error('An error occurred during upload', delErr)
32 | }
33 | console.log(`Deleting result: ${delBody}`)
34 |
35 | notes.map(note => {
36 | request(
37 | {
38 | uri: `${FIREBASE_URL}/notes.json`,
39 | method: 'POST',
40 | body: JSON.stringify(note),
41 | timeout: 10000,
42 | },
43 | (error, response, body) => {
44 | if (error) {
45 | console.error('An error occurred during upload', error)
46 | }
47 | console.log(`Insert result: ${body}`)
48 | }
49 | )
50 | })
51 | }
52 | )
53 |
--------------------------------------------------------------------------------
/lesson-5/README.md:
--------------------------------------------------------------------------------
1 | # React router / redux
2 |
3 | ## Goal - understand basics of react router and redux
4 |
5 | ## Workshop structure
6 |
7 | * [goal] add second page showing summary of all notes, that can be accessed from menu in header
8 | * [exercise - refactor] move logic from App.js to separate component - EditNotesPage.js
9 | 1. create component EditNotesPage and move logic from App.js to this new component (this breaks everything for now)
10 | 1. create folder editNotesPage and move all related components to it
11 | 1. fix paths in moved components, so it builds
12 | * [theory] react router
13 | * [demo] add one link to header and point it to EditNotesPage
14 | * [exercise] create second page with summary
15 | 1. create new folder summaryPage
16 | 1. create new component summaryPage and make it display titles of all notes (won't work for now, we broke stuff in our refactor)
17 | 1. make the new component display some dummy data for now
18 | 1. npm i --save 'react-router-dom'
19 | 1. add link to the header and make it work
20 | * [theory] redux
21 | 1. [overview](https://i.stack.imgur.com/LNQwH.png)
22 | 1. [three principles](https://redux.js.org/docs/introduction/ThreePrinciples.html)
23 | 1. [why to use redux](https://cdn.css-tricks.com/wp-content/uploads/2016/03/redux-article-3-03.svg)
24 | 1. disadvantages
25 | * [demo] add redux to to editNotesPage
26 | * [demo] combining reducers
27 | * [exercise]
28 | 1. npm install --save react-redux
29 | 1. add redux to header
30 | * [theory] redux vs local state
31 |
32 | ## Changes since end of workshop
33 |
34 | * added [redux-logger](https://github.com/evgenyrodionov/redux-logger) package (dont forget to run 'npm i'), so if you open your browser console, you will see every state change there, [like so](https://user-images.githubusercontent.com/3807458/30935862-4bbd3094-a3d2-11e7-8702-c6222fedc78f.png)
35 | * removed combining reducers - everything is now in Reducer.js (it was unnecessary so far and confusing)
36 | * action types are now string constants inside Reducer.js - it's very recommended to use constants instead of typing it in every time
37 | * connected NewNoteModal.js to redux store and added NOTE_ADDED action - notice how action is dispatched there
38 |
39 | ## Exercise @home - connect Header to redux store and make it display number of notes
40 | * it's better to pass to the component only number of notes - this components doesn't need to have access to whole array
41 |
42 | ## Exercise @home - connect SummaryPage to redux store and make it display all note titles as a list
43 |
44 | ## Exercise @home (for bonus points) - add MAKE ALL CAPS button
45 | * add MAKE ALL CAPS button next to edit and remove buttons, that will make make title of the note all UPPER CASE
46 | * it's useful if you want to easily make a note REALLY IMPORTANT
47 | * should be done through new action, with action.type = 'NOTE_MAKE_TITLE_ALL_CAPS'
48 |
49 |
50 |
51 | ## Learning resources
52 |
53 | * [HTML5 history API](https://developer.mozilla.org/en-US/docs/Web/API/History)
54 | * [React router v4 documentation](https://reacttraining.com/react-router/web/guides/philosophy)
55 | * [Redux guide](https://redux.js.org/docs/introduction/)
56 | * [Explain Redux like I'm five](https://dev.to/hemanth/explain-redux-like-im-five)
57 | * [You Might Not Need Redux](https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367)
--------------------------------------------------------------------------------
/lesson-5/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "15.6.0",
7 | "react": "16.0.0",
8 | "react-dom": "16.0.0",
9 | "react-modal": "3.0.3",
10 | "react-redux": "^5.0.6",
11 | "react-router-dom": "^4.2.2",
12 | "redux": "^3.7.2",
13 | "redux-logger": "^3.0.6",
14 | "request": "2.83.0",
15 | "shortid": "2.2.8"
16 | },
17 | "devDependencies": {
18 | "prettier": "1.7.4",
19 | "react-scripts": "1.0.14"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test --env=jsdom",
25 | "eject": "react-scripts eject"
26 | },
27 | "prettier": {
28 | "printWidth": 100,
29 | "tabWidth": 2,
30 | "useTabs": false,
31 | "semi": false,
32 | "bracketSpacing": true,
33 | "jsxBracketSameLine": true,
34 | "singleQuote": true,
35 | "trailingComma": "es5"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lesson-5/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-5/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-5/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-5/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-5/src/Reducer.js:
--------------------------------------------------------------------------------
1 | export const actionTypes = {
2 | NOTE_ADDED: 'NOTE_ADDED',
3 | NOTE_UPDATED: 'NOTE_UPDATED',
4 | NOTE_DELETED: 'NOTE_DELETED'
5 | }
6 |
7 | const defaultState = {
8 | notes: [
9 | {
10 | title: 'MSD Code Academy',
11 | text: "Let's crete React app with a few components.",
12 | uuid: 1,
13 | }
14 | ]
15 | }
16 |
17 | export default function Reducer(state = defaultState, action) {
18 | switch (action.type) {
19 |
20 | case actionTypes.NOTE_ADDED:
21 | const newNote = action.newNote
22 | let newNotes = { ...state, notes: [...state.notes, newNote] };
23 | return newNotes
24 |
25 | case actionTypes.NOTE_UPDATED:
26 | const editedNote = action.editedNote
27 | newNotes = state.notes.map((note, index) => note.uuid === editedNote.uuid ? editedNote : note);
28 | return { ...state, notes: newNotes }
29 |
30 | case actionTypes.NOTE_DELETED:
31 | const deletedNoteUuid = action.deletedNoteUuid
32 | newNotes = state.notes.filter(note => {
33 | return note.uuid !== deletedNoteUuid
34 | })
35 | return { ...state, notes: newNotes }
36 |
37 | default:
38 | return state;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lesson-5/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route } from 'react-router-dom'
3 | import Header from './Header'
4 | import EditNotesPage from './editNotesPage/EditNotesPage'
5 | import SummaryPage from './summaryPage/SummaryPage'
6 | import Footer from './Footer'
7 |
8 | import '../styles/App.css'
9 |
10 | class App extends React.Component {
11 |
12 |
13 | render() {
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default App
27 |
--------------------------------------------------------------------------------
/lesson-5/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-5/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | import NewNoteModal from './NewNoteModal'
5 | import logo from '../logo.png'
6 | import '../styles/Header.css'
7 |
8 | class Header extends React.Component {
9 |
10 | getNoteCountMessage(noteCount){
11 | const isPlural = noteCount > 1;
12 | return `(containing ${noteCount} idea${isPlural ? 's' : ''})`
13 | }
14 |
15 | render() {
16 | const { ...props } = this.props
17 | return (
18 |
19 |
20 |

21 |
IDEA JOURNAL
22 |
List Notes
23 |
Summary
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default Header
33 |
--------------------------------------------------------------------------------
/lesson-5/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import { connect } from 'react-redux'
4 |
5 | import * as shortId from 'shortid'
6 | import '../styles/NewNoteModal.css'
7 | import { actionTypes } from '../Reducer'
8 |
9 |
10 | class NewNoteModal extends React.Component {
11 | constructor() {
12 | super()
13 | this.state = {
14 | modalIsOpen: false,
15 | note: {},
16 | }
17 | }
18 |
19 | toggleModal = () => {
20 | this.setState({
21 | modalIsOpen: !this.state.modalIsOpen,
22 | })
23 | }
24 |
25 | handleChange = field => e => {
26 | let note = this.state.note
27 | note[field] = e.target.value
28 | this.setState({ note })
29 | }
30 |
31 | handleFormSubmit = e => {
32 | e.preventDefault()
33 | const { onAddNote } = this.props
34 | const { note } = this.state
35 | const newNote = { ...note, uuid: shortId.generate() }
36 | this.props.dispatch({ type: actionTypes.NOTE_ADDED, newNote })
37 | this.toggleModal()
38 | }
39 |
40 | render() {
41 | const { modalIsOpen } = this.state
42 | return (
43 |
44 |
ADD NOTE
45 |
51 | Add a new note
52 |
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | // we are not interested in any updates, we just want to add new notes to store
70 | function mapStateToProps(state) {
71 | return {}
72 | }
73 |
74 | export default connect(mapStateToProps)(NewNoteModal);
--------------------------------------------------------------------------------
/lesson-5/src/components/editNotesPage/EditNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import '../../styles/EditNoteModal.css'
4 |
5 | class EditNoteModal extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | modalIsOpen: false,
10 | note: {
11 | uuid: props.noteUuid,
12 | text: props.text,
13 | title: props.title,
14 | },
15 | }
16 | }
17 |
18 | toggleModal = () => {
19 | this.setState({
20 | modalIsOpen: !this.state.modalIsOpen,
21 | })
22 | }
23 |
24 | handleChange = field => e => {
25 | let note = this.state.note
26 | note[field] = e.target.value
27 | this.setState({ note })
28 | }
29 |
30 | handleFormSubmit = e => {
31 | e.preventDefault()
32 | this.props.editNote(this.state.note)
33 | this.toggleModal()
34 | }
35 |
36 | render() {
37 | const { modalIsOpen } = this.state
38 | return (
39 |
40 |
edit
41 |
47 | Edit a note
48 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default EditNoteModal
76 |
--------------------------------------------------------------------------------
/lesson-5/src/components/editNotesPage/EditNotesPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NoteList from './NoteList'
3 | import { actionTypes } from '../../Reducer'
4 |
5 | import { connect } from 'react-redux'
6 |
7 | class EditNotesPage extends React.Component {
8 |
9 |
10 |
11 | editNote = editedNote => {
12 | this.props.dispatch({ type: actionTypes.NOTE_UPDATED, editedNote })
13 | }
14 |
15 | removeNoteFromList = deletedNoteUuid => () => {
16 | this.props.dispatch({ type: actionTypes.NOTE_DELETED, deletedNoteUuid })
17 | }
18 |
19 | render() {
20 | const notes = this.props.notes
21 | const noteCount = notes.length
22 | return (
)
26 | }
27 |
28 | }
29 |
30 | function mapStateToProps(state) {
31 | return {
32 | notes: state.notes
33 | };
34 | }
35 |
36 | export default connect(mapStateToProps)(EditNotesPage);
--------------------------------------------------------------------------------
/lesson-5/src/components/editNotesPage/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import EditNoteModal from './EditNoteModal'
4 | import '../../styles/Note.css'
5 |
6 | class Note extends React.Component {
7 | state = {
8 | showMore: false,
9 | }
10 |
11 | handleShowMore = () => {
12 | this.setState({
13 | showMore: !this.state.showMore,
14 | })
15 | }
16 |
17 | render() {
18 | const { noteUuid, title, text, removeNoteFromList } = this.props
19 | const { showMore } = this.state
20 | return (
21 |
32 | )
33 | }
34 | }
35 |
36 | export default Note
37 |
--------------------------------------------------------------------------------
/lesson-5/src/components/editNotesPage/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const { notes, editNote, removeNoteFromList } = this.props
8 | return (
9 |
10 | {notes.map(note => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default NoteList
26 |
--------------------------------------------------------------------------------
/lesson-5/src/components/summaryPage/summaryPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import '../../styles/SummaryPage.css'
4 |
5 | class SummaryPage extends React.Component {
6 | render() {
7 | return (
8 |
9 | Hi! Im a summary page
10 |
11 | )
12 | }
13 |
14 | }
15 |
16 | export default SummaryPage
--------------------------------------------------------------------------------
/lesson-5/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom'
4 |
5 | import { createStore, applyMiddleware } from 'redux'
6 | import { Provider } from "react-redux";
7 | import logger from 'redux-logger'
8 |
9 | import './styles/index.css';
10 | import App from './components/App';
11 | import registerServiceWorker from './registerServiceWorker';
12 |
13 | import Reducer from './Reducer'
14 |
15 | let store = createStore(Reducer, applyMiddleware(logger))
16 |
17 | ReactDOM.render(
18 |
19 |
20 |
21 |
22 | , document.getElementById('root'));
23 | registerServiceWorker();
24 |
--------------------------------------------------------------------------------
/lesson-5/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-5/src/logo.png
--------------------------------------------------------------------------------
/lesson-5/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/EditNoteModal.css:
--------------------------------------------------------------------------------
1 | .EditNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .EditNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .EditNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .EditNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .EditNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .EditNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .EditNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .EditNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .EditNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .EditNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .EditNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .EditNoteModal-modal-window input[type="text"],
76 | .EditNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
14 |
15 | .Header-logo span {
16 | margin: 10px
17 | }
18 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/SummaryPage.css:
--------------------------------------------------------------------------------
1 | .Summary {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-5/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-6/README.md:
--------------------------------------------------------------------------------
1 | # Webpack
2 |
3 | ## Goal
4 | - Understand redux enough so I know when to use it and how
5 | - Know how Webpack can help me
6 |
7 | ## Redux recap
8 |
9 | - Review TODO App Redux edition
10 | - TODO:// Add Summary page
11 | - Show newest note added
12 | - Show total number of notes
13 |
14 | ## Webpack
15 |
16 | ## Why it is useful
17 |
18 | *index-1.html*
19 | ```
20 | ...
21 |
22 |
23 | ...
24 |
25 |
26 |
27 | ...
28 | ```
29 |
30 | *index-2.html*
31 | ```
32 | ...
33 |
34 | ...
35 | ```
36 |
37 | ## Grunt vs Webpack
38 |
39 | ```
40 | grunt.initConfig({
41 | concat: {
42 | options: {
43 | separator: ';'
44 | },
45 | dist: {
46 | src: ['src/**/*.js'],
47 | dest: 'dist/<%= pkg.name %>.js'
48 | }
49 | },
50 | watch: {
51 | files: ['<%= js.files %>'],
52 | tasks: ['concat']
53 | }
54 | });
55 | ```
56 |
57 | ```
58 | module.exports = {
59 | entry: [
60 | './src/app/index.js'
61 | ],
62 | module: {
63 | loaders: [
64 | {
65 | test: /\.js$/,
66 | include: __dirname + '/app',
67 | loader: 'babel?presets[]=es2015'
68 | }
69 | ]
70 | },
71 | output: {
72 | filename: 'index_bundle.js',
73 | path: __dirname + '/dist'
74 | }
75 | }
76 | ```
77 |
78 | ## Loaders
79 |
80 |
81 |
82 | ### CSS Loader
83 |
84 | ### File Loader
85 |
86 | ## Babel
87 |
88 |
89 |
90 | ## React Create App
91 |
92 | ### Eject
93 |
94 | ## Learning resources
95 |
96 | * []()
97 |
--------------------------------------------------------------------------------
/lesson-6/todos/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "15.6.0",
7 | "react": "16.0.0",
8 | "react-dom": "16.0.0",
9 | "react-modal": "3.0.3",
10 | "react-redux": "^5.0.6",
11 | "react-router-dom": "^4.2.2",
12 | "redux": "^3.7.2",
13 | "redux-logger": "^3.0.6",
14 | "request": "2.83.0",
15 | "shortid": "2.2.8"
16 | },
17 | "devDependencies": {
18 | "prettier": "1.7.4",
19 | "react-scripts": "1.0.14"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test --env=jsdom",
25 | "eject": "react-scripts eject"
26 | },
27 | "prettier": {
28 | "printWidth": 100,
29 | "tabWidth": 2,
30 | "useTabs": false,
31 | "semi": false,
32 | "bracketSpacing": true,
33 | "jsxBracketSameLine": true,
34 | "singleQuote": true,
35 | "trailingComma": "es5"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/lesson-6/todos/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-6/todos/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-6/todos/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-6/todos/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/Reducer.js:
--------------------------------------------------------------------------------
1 | export const actionTypes = {
2 | NOTE_ADDED: 'NOTE_ADDED',
3 | NOTE_UPDATED: 'NOTE_UPDATED',
4 | NOTE_DELETED: 'NOTE_DELETED'
5 | }
6 |
7 | const defaultState = {
8 | notes: [
9 | {
10 | title: 'MSD Code Academy',
11 | text: "Let's crete React app with a few components.",
12 | uuid: 1,
13 | }
14 | ]
15 | }
16 |
17 | export default function Reducer(state = defaultState, action) {
18 | switch (action.type) {
19 |
20 | case actionTypes.NOTE_ADDED:
21 | const newNote = action.newNote
22 | let newNotes = { ...state, notes: [...state.notes, newNote] };
23 | return newNotes
24 |
25 | case actionTypes.NOTE_UPDATED:
26 | const editedNote = action.editedNote
27 | newNotes = state.notes.map((note, index) => note.uuid === editedNote.uuid ? editedNote : note);
28 | return { ...state, notes: newNotes }
29 |
30 | case actionTypes.NOTE_DELETED:
31 | const deletedNoteUuid = action.deletedNoteUuid
32 | newNotes = state.notes.filter(note => {
33 | return note.uuid !== deletedNoteUuid
34 | })
35 | return { ...state, notes: newNotes }
36 |
37 | default:
38 | return state;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route } from 'react-router-dom'
3 | import Header from './Header'
4 | import EditNotesPage from './editNotesPage/EditNotesPage'
5 | import SummaryPage from './summaryPage/SummaryPage'
6 | import Footer from './Footer'
7 |
8 | import '../styles/App.css'
9 |
10 | class App extends React.Component {
11 |
12 |
13 | render() {
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default App
27 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | import NewNoteModal from './NewNoteModal'
5 | import logo from '../logo.png'
6 | import '../styles/Header.css'
7 |
8 | class Header extends React.Component {
9 |
10 | getNoteCountMessage(noteCount){
11 | const isPlural = noteCount > 1;
12 | return `(containing ${noteCount} idea${isPlural ? 's' : ''})`
13 | }
14 |
15 | render() {
16 | const { ...props } = this.props
17 | return (
18 |
19 |
20 |

21 |
IDEA JOURNAL
22 |
List Notes
23 |
Summary
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default Header
33 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import { connect } from 'react-redux'
4 |
5 | import * as shortId from 'shortid'
6 | import '../styles/NewNoteModal.css'
7 | import { actionTypes } from '../Reducer'
8 |
9 |
10 | class NewNoteModal extends React.Component {
11 | constructor() {
12 | super()
13 | this.state = {
14 | modalIsOpen: false,
15 | note: {},
16 | }
17 | }
18 |
19 | toggleModal = () => {
20 | this.setState({
21 | modalIsOpen: !this.state.modalIsOpen,
22 | })
23 | }
24 |
25 | handleChange = field => e => {
26 | let note = this.state.note
27 | note[field] = e.target.value
28 | this.setState({ note })
29 | }
30 |
31 | handleFormSubmit = e => {
32 | e.preventDefault()
33 | const { onAddNote } = this.props
34 | const { note } = this.state
35 | const newNote = { ...note, uuid: shortId.generate() }
36 | this.props.dispatch({ type: actionTypes.NOTE_ADDED, newNote })
37 | this.toggleModal()
38 | }
39 |
40 | render() {
41 | const { modalIsOpen } = this.state
42 | return (
43 |
44 |
ADD NOTE
45 |
51 | Add a new note
52 |
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | // we are not interested in any updates, we just want to add new notes to store
70 | function mapStateToProps(state) {
71 | return {}
72 | }
73 |
74 | export default connect(mapStateToProps)(NewNoteModal);
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/editNotesPage/EditNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import '../../styles/EditNoteModal.css'
4 |
5 | class EditNoteModal extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | modalIsOpen: false,
10 | note: {
11 | uuid: props.noteUuid,
12 | text: props.text,
13 | title: props.title,
14 | },
15 | }
16 | }
17 |
18 | toggleModal = () => {
19 | this.setState({
20 | modalIsOpen: !this.state.modalIsOpen,
21 | })
22 | }
23 |
24 | handleChange = field => e => {
25 | let note = this.state.note
26 | note[field] = e.target.value
27 | this.setState({ note })
28 | }
29 |
30 | handleFormSubmit = e => {
31 | e.preventDefault()
32 | this.props.editNote(this.state.note)
33 | this.toggleModal()
34 | }
35 |
36 | render() {
37 | const { modalIsOpen } = this.state
38 | return (
39 |
40 |
edit
41 |
47 | Edit a note
48 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default EditNoteModal
76 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/editNotesPage/EditNotesPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NoteList from './NoteList'
3 | import { actionTypes } from '../../Reducer'
4 |
5 | import { connect } from 'react-redux'
6 |
7 | class EditNotesPage extends React.Component {
8 |
9 |
10 |
11 | editNote = editedNote => {
12 | this.props.dispatch({ type: actionTypes.NOTE_UPDATED, editedNote })
13 | }
14 |
15 | removeNoteFromList = deletedNoteUuid => () => {
16 | this.props.dispatch({ type: actionTypes.NOTE_DELETED, deletedNoteUuid })
17 | }
18 |
19 | render() {
20 | const notes = this.props.notes
21 | const noteCount = notes.length
22 | return (
)
26 | }
27 |
28 | }
29 |
30 | function mapStateToProps(state) {
31 | return {
32 | notes: state.notes
33 | };
34 | }
35 |
36 | export default connect(mapStateToProps)(EditNotesPage);
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/editNotesPage/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import EditNoteModal from './EditNoteModal'
4 | import '../../styles/Note.css'
5 |
6 | class Note extends React.Component {
7 | state = {
8 | showMore: false,
9 | }
10 |
11 | handleShowMore = () => {
12 | this.setState({
13 | showMore: !this.state.showMore,
14 | })
15 | }
16 |
17 | render() {
18 | const { noteUuid, title, text, removeNoteFromList } = this.props
19 | const { showMore } = this.state
20 | return (
21 |
32 | )
33 | }
34 | }
35 |
36 | export default Note
37 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/editNotesPage/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const { notes, editNote, removeNoteFromList } = this.props
8 | return (
9 |
10 | {notes.map(note => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default NoteList
26 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/components/summaryPage/SummaryPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import '../../styles/SummaryPage.css'
5 |
6 | class SummaryPage extends React.Component {
7 | render() {
8 | return (
9 |
10 | Number of notes: {this.props.notes.length}
11 |
12 | )
13 | }
14 |
15 | }
16 |
17 | function mapStateToProps(state) {
18 | return {
19 | notes: state.notes
20 | }
21 | }
22 |
23 | export default connect(mapStateToProps)(SummaryPage);
--------------------------------------------------------------------------------
/lesson-6/todos/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom'
4 |
5 | import { createStore, applyMiddleware } from 'redux'
6 | import { Provider } from "react-redux";
7 | import logger from 'redux-logger'
8 |
9 | import './styles/index.css';
10 | import App from './components/App';
11 | import registerServiceWorker from './registerServiceWorker';
12 |
13 | import Reducer from './Reducer'
14 |
15 | let store = createStore(Reducer, applyMiddleware(logger))
16 |
17 | ReactDOM.render(
18 |
19 |
20 |
21 |
22 | , document.getElementById('root'));
23 | registerServiceWorker();
24 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-6/todos/src/logo.png
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/EditNoteModal.css:
--------------------------------------------------------------------------------
1 | .EditNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .EditNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .EditNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .EditNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .EditNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .EditNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .EditNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .EditNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .EditNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .EditNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .EditNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .EditNoteModal-modal-window input[type="text"],
76 | .EditNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
14 |
15 | .Header-logo span {
16 | margin: 10px
17 | }
18 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/SummaryPage.css:
--------------------------------------------------------------------------------
1 | .Summary {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-6/todos/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-6/webpack/app.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-6/webpack/app.js
--------------------------------------------------------------------------------
/lesson-6/webpack/hello.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-6/webpack/hello.js
--------------------------------------------------------------------------------
/lesson-6/webpack/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
![]()
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lesson-6/webpack/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-6/webpack/logo.png
--------------------------------------------------------------------------------
/lesson-6/webpack/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lesson-6-webpack",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "app.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "build": "webpack"
9 | },
10 | "author": "",
11 | "license": "ISC",
12 | "devDependencies": {
13 | "babel-core": "^6.26.0",
14 | "babel-loader": "^7.1.2",
15 | "babel-preset-es2015": "^6.24.1",
16 | "css-loader": "^0.28.7",
17 | "file-loader": "^1.1.5",
18 | "style-loader": "^0.19.0",
19 | "url-loader": "^0.6.2",
20 | "webpack": "^3.8.1"
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/lesson-6/webpack/styles.css:
--------------------------------------------------------------------------------
1 | #name {
2 | color: red;
3 | }
--------------------------------------------------------------------------------
/lesson-6/webpack/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | entry: [
3 | './app.js'
4 | ],
5 | module: {
6 | loaders: [
7 | {
8 | test: /\.js$/,
9 | include: __dirname,
10 | loader: 'babel-loader?presets[]=es2015'
11 | },
12 | {
13 | test: /\.css$/,
14 | use: [ 'style-loader', 'css-loader' ]
15 | },
16 | {
17 | test: /\.(jpg|png|svg)$/,
18 | use: [
19 | {
20 | loader: 'url-loader',
21 | options: {
22 | limit: 80000
23 | }
24 | }
25 | ]
26 | }
27 | ]
28 | },
29 | output: {
30 | filename: 'bundle.js',
31 | path: __dirname + '/dist'
32 | }
33 | }
--------------------------------------------------------------------------------
/lesson-7/server/api.js:
--------------------------------------------------------------------------------
1 | const mongodb = require('mongodb');
2 |
3 | let database;
4 | let collectionName;
5 |
6 | function initConnectionPool(url, collection = 'todos') {
7 | return mongodb.MongoClient.connect(url).then(db => {
8 | database = db;
9 | collectionName = collection;
10 | });
11 | }
12 |
13 | function closeConnectionPool() {
14 | if(database) {
15 | return database.close();
16 | database = undefined;
17 | } else {
18 | return Promise.resolve();
19 | }
20 | }
21 |
22 | function resetCollection() {
23 | return database.collection(collectionName).drop();
24 | }
25 |
26 | // TODO: impelment CRUD operations on todos collection
27 |
28 | function getTodoItems() {
29 |
30 | }
31 |
32 | function getTodoItem(id) {
33 |
34 | }
35 |
36 | function createTodoItem(id, item) {
37 |
38 | }
39 |
40 | function updateTodoItem(id, item) {
41 |
42 | }
43 |
44 | function deleteTodoItem(id) {
45 |
46 | }
47 |
48 | module.exports = {
49 | initConnectionPool,
50 | closeConnectionPool,
51 | getTodoItems,
52 | getTodoItem,
53 | createTodoItem,
54 | updateTodoItem,
55 | deleteTodoItem,
56 | resetCollection
57 | }
--------------------------------------------------------------------------------
/lesson-7/server/app.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const bodyParser = require('body-parser');
3 | const api = require('./api');
4 | const config = require('./config');
5 |
6 | const requestHandler = require('./requestHandler')
7 | const handleRequest = requestHandler.handleRequest;
8 | const errorHandler = requestHandler.handleError;
9 |
10 | const app = express();
11 | app.use(bodyParser.json());
12 |
13 | app.use(errorHandler);
14 |
15 | /* TODO: add REST endpoints */
16 |
17 | api.initConnectionPool(config.MONGO_URL)
18 | .then(() => {
19 | // start the server
20 | app.listen(8080, function () {
21 | console.log('listening on 8080');
22 | });
23 | }).catch(error => {
24 | console.error(`connection to db failed: ${error}`);
25 | });
26 |
--------------------------------------------------------------------------------
/lesson-7/server/config.js:
--------------------------------------------------------------------------------
1 | const MONGO_DB_NAME = 'test';
2 |
3 | module.exports = {
4 | MONGO_URL: `mongodb://codeacademy:2PpaNSPlh3C9pylF@cluster0-shard-00-00-k2lgb.mongodb.net:27017,cluster0-shard-00-01-k2lgb.mongodb.net:27017,cluster0-shard-00-02-k2lgb.mongodb.net:27017/${MONGO_DB_NAME}?ssl=true&replicaSet=Cluster0-shard-0&authSource=admin`,
5 | }
--------------------------------------------------------------------------------
/lesson-7/server/e2e-test/e2e.js:
--------------------------------------------------------------------------------
1 | const request = require('supertest');
2 | const chai = require('chai');
3 |
4 | const todosSchema = require('./todosSchema.json');
5 |
6 | chai.use(require('chai-json-schema'));
7 | const url = 'http://localhost:8080';
8 |
9 | describe('invoke /todos resource', () => {
10 | it('should return a payload and a successful status code', () => {
11 | request(url)
12 | .get(`/todos`)
13 | .expect(200)
14 | .expect('content-type', 'application/json; charset=utf-8')
15 | .then((res) => {
16 | chai.expect(res.body).to.not.be.undefined;
17 | chai.expect(res.body).to.be.jsonSchema(todosSchema);
18 | });
19 | });
20 | });
--------------------------------------------------------------------------------
/lesson-7/server/e2e-test/todosSchema.json:
--------------------------------------------------------------------------------
1 | {
2 | "definitions": {},
3 | "$schema": "http://json-schema.org/draft-06/schema#",
4 | "$id": "http://example.com/example.json",
5 | "type": "array",
6 | "items": {
7 | "$id": "http://example.com/example.json/items",
8 | "type": "object",
9 | "properties": {
10 | "_id": {
11 | "$id": "http://example.com/example.json/items/properties/_id",
12 | "type": "string"
13 | },
14 | "title": {
15 | "$id": "http://example.com/example.json/items/properties/title",
16 | "type": "string"
17 | },
18 | "text": {
19 | "$id": "http://example.com/example.json/items/properties/text",
20 | "type": "string"
21 | },
22 | "uuid": {
23 | "$id": "http://example.com/example.json/items/properties/uuid",
24 | "type": "string"
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/lesson-7/server/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todos-server",
3 | "version": "1.0.0",
4 | "description": "server for todo items",
5 | "main": "app.js",
6 | "scripts": {
7 | "start": "node app.js",
8 | "dev": "nodemon app.js",
9 | "test": "mocha ./test --timeout 10000",
10 | "test-e2e": "mocha ./e2e-test --timeout 10000"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "https://github.com/msd-code-academy"
15 | },
16 | "author": "MSD Code Academy",
17 | "license": "ISC",
18 | "dependencies": {
19 | "body-parser": "^1.18.2",
20 | "express": "^4.16.2",
21 | "mongodb": "^2.2.33"
22 | },
23 | "devDependencies": {
24 | "chai": "^4.1.2",
25 | "chai-json-schema": "^1.5.0",
26 | "chai-spies-next": "^0.9.3",
27 | "mocha": "^4.0.1",
28 | "nodemon": "^1.12.1",
29 | "sinon": "^4.1.2",
30 | "sinon-stub-promise": "^4.0.0",
31 | "supertest": "^3.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/lesson-7/server/requestHandler.js:
--------------------------------------------------------------------------------
1 | function handleRequest(action, req, res, next) {
2 | return action().then(result => {
3 | res.json(result);
4 | }).catch((error) => {
5 | next(error);
6 | });
7 | }
8 |
9 | function handleError(err, req, res, next) {
10 | console.error(err);
11 | res.status(500);
12 | res.json(err);
13 | }
14 |
15 | module.exports = {
16 | handleRequest,
17 | handleError
18 | }
19 |
--------------------------------------------------------------------------------
/lesson-7/server/test/api.js:
--------------------------------------------------------------------------------
1 | const chai = require('chai');
2 | const api = require('../api');
3 | const config = require('../config');
4 | const collection = 'todos_TEST'; // use separate collection for tests
5 |
6 | const testItems = [
7 | {
8 | _id: 1,
9 | text: 'test1'
10 | },
11 | {
12 | _id: 2,
13 | text: 'test2'
14 | },
15 | {
16 | _id: 3,
17 | text: 'test3'
18 | }
19 | ];
20 |
21 | describe('todo API Tests', () => {
22 | before(() => {
23 | // initialize the api
24 | return api.initConnectionPool(config.MONGO_URL, collection)
25 | .then(() => api.resetCollection().catch(error => { console.log(error) }))
26 | .then(() =>
27 | // create test items
28 | Promise.all(testItems.map((item) => api.createTodoItem(item._id, item))));
29 | });
30 |
31 | after(() => api.closeConnectionPool());
32 |
33 | describe('test getTodoItems', () => {
34 | it('gets all test items', () => {
35 | return api.getTodoItems().then(items => {
36 | chai.expect(items).to.deep.equal(testItems);
37 | })
38 | })
39 | });
40 |
41 | describe('test getTodoItem', () => {
42 | it('gets todo item by id', () => {
43 | return api.getTodoItem(testItems[0]._id).then(item => {
44 | chai.expect(item).to.deep.equal(testItems[0]);
45 | })
46 | })
47 | });
48 |
49 | describe('test createTodoItem', () => {
50 | it('creates new todo item and retrieves it', () => {
51 | const newItem = {
52 | _id: 4,
53 | text: "test4"
54 | }
55 |
56 | return api.createTodoItem(newItem._id, newItem)
57 | .then(() => {
58 | api.getTodoItem(newItem._id).then(item => {
59 | chai.expect(item).to.deep.equal(newItem);
60 | })
61 | });
62 | })
63 | });
64 |
65 | describe('test updateTodoItem', () => {
66 | it('updates an existing todo item and retrieves it', () => {
67 | const updatedItem = { ...testItems[0] }
68 | updatedItem.text = "test1 updated";
69 |
70 | return api.updateTodoItem(updatedItem._id, updatedItem)
71 | .then(() => {
72 | api.getTodoItem(updatedItem._id).then(item => {
73 | chai.expect(item).to.deep.equal(updatedItem);
74 | })
75 | });
76 | })
77 | });
78 |
79 | describe('test deleteTodoItem', () => {
80 | it('deletes an existing todo item and checks it is not there anymore', () => {
81 | const deletedItem = { ...testItems[0] }
82 |
83 | return api.deleteTodoItem(deletedItem._id)
84 | .then(() => {
85 | api.getTodoItem(deletedItem._id).then(item => {
86 | chai.expect(item).to.be.null;
87 | })
88 | });
89 | })
90 | });
91 | });
92 |
--------------------------------------------------------------------------------
/lesson-7/server/test/requestHandler.js:
--------------------------------------------------------------------------------
1 | const sinon = require('sinon');
2 | const sinonStubPromise = require('sinon-stub-promise');
3 | const chai = require('chai');
4 | const spies = require('chai-spies-next');
5 |
6 | const requestHandler = require('../requestHandler');
7 |
8 | chai.use(spies);
9 | sinonStubPromise(sinon);
10 |
11 | describe('requestHandler tests', () => {
12 | it('handles successful results', (done) => {
13 | const reqMock = {};
14 | const resMock = {};
15 | resMock.json = chai.spy();
16 | const nextSpy = chai.spy();
17 | const testResult = {
18 | 'result': 'ok',
19 | 'status': '200'
20 | }
21 | const actionStub = sinon.stub().returnsPromise().resolves(testResult);
22 |
23 | requestHandler.handleRequest(actionStub, reqMock, resMock, nextSpy).then(() => {
24 | chai.expect(resMock.json).to.have.been.called.with(testResult);
25 | chai.expect(nextSpy).to.not.have.been.called();
26 |
27 | done();
28 | }).catch(error => {
29 | done(error);
30 | })
31 | });
32 |
33 | it('handles error results', (done) => {
34 | // TODO: test error response case of requestHandler.handleRequest
35 | })
36 | });
--------------------------------------------------------------------------------
/lesson-7/todos/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idea-journal",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "15.6.0",
7 | "react": "16.0.0",
8 | "react-dom": "16.0.0",
9 | "react-modal": "3.0.3",
10 | "react-redux": "^5.0.6",
11 | "react-router-dom": "^4.2.2",
12 | "redux": "^3.7.2",
13 | "redux-logger": "^3.0.6",
14 | "redux-thunk": "^2.2.0",
15 | "request": "2.83.0",
16 | "shortid": "2.2.8"
17 | },
18 | "devDependencies": {
19 | "prettier": "1.7.4",
20 | "react-scripts": "1.0.14"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test --env=jsdom",
26 | "eject": "react-scripts eject"
27 | },
28 | "prettier": {
29 | "printWidth": 100,
30 | "tabWidth": 2,
31 | "useTabs": false,
32 | "semi": false,
33 | "bracketSpacing": true,
34 | "jsxBracketSameLine": true,
35 | "singleQuote": true,
36 | "trailingComma": "es5"
37 | },
38 | "proxy": {
39 | "/todos": { "target": "http://localhost:8080/" }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/lesson-7/todos/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-7/todos/public/favicon.ico
--------------------------------------------------------------------------------
/lesson-7/todos/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 |
React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/lesson-7/todos/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/Reducer.js:
--------------------------------------------------------------------------------
1 | import * as api from './api'
2 |
3 | export const actionTypes = {
4 | // TODO: add async action types
5 |
6 | NOTE_ADDED: 'NOTE_ADDED',
7 | NOTE_UPDATED: 'NOTE_UPDATED',
8 | NOTE_DELETED: 'NOTE_DELETED'
9 | }
10 |
11 | const defaultState = {
12 | notes: []
13 | }
14 |
15 | export function fetchNotesAsync() {
16 | // TODO: implement
17 | }
18 |
19 | export default function Reducer(state = defaultState, action) {
20 | switch (action.type) {
21 | // TODO: add async action types
22 |
23 | case actionTypes.NOTE_ADDED:
24 | const newNote = action.newNote;
25 | api.addTodo(newNote);
26 | let newNotes = { ...state, notes: [...state.notes, newNote] };
27 | return newNotes
28 |
29 | case actionTypes.NOTE_UPDATED:
30 | const editedNote = action.editedNote;
31 | api.updateTodo(editedNote);
32 | newNotes = state.notes.map((note, index) => note.uuid === editedNote.uuid ? editedNote : note);
33 | return { ...state, notes: newNotes }
34 |
35 | case actionTypes.NOTE_DELETED:
36 | const deletedNoteUuid = action.deletedNoteUuid;
37 | api.deleteTodo(deletedNoteUuid);
38 | newNotes = state.notes.filter(note => {
39 | return note.uuid !== deletedNoteUuid
40 | })
41 | return { ...state, notes: newNotes }
42 |
43 | default:
44 | return state;
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/api.js:
--------------------------------------------------------------------------------
1 | import * as config from './config/api'
2 |
3 | export function getTodos() {
4 | return fetch(`${config.BACKEND_URL}/todos`)
5 | .then(result => {
6 | return result.json();
7 | });
8 | }
9 |
10 | export function addTodo(item) {
11 | return fetch(`${config.BACKEND_URL}/todos/${item.uuid}`, {
12 | method: 'POST',
13 | headers: { 'Content-Type': 'application/json' },
14 | body: JSON.stringify(item)
15 | }).then(result => {
16 | return result.json();
17 | });
18 | }
19 |
20 | export function updateTodo(item) {
21 | return fetch(`${config.BACKEND_URL}/todos/${item.uuid}`, {
22 | method: 'PUT',
23 | headers: { 'Content-Type': 'application/json' },
24 | body: JSON.stringify(item)
25 | }).then(result => {
26 | return result.json();
27 | });
28 | }
29 |
30 | export function deleteTodo(uuid) {
31 | return fetch(`${config.BACKEND_URL}/todos/${uuid}`, {
32 | method: 'DELETE'
33 | }).then(result => {
34 | return result.json();
35 | });
36 | }
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Route } from 'react-router-dom'
3 | import Header from './Header'
4 | import EditNotesPage from './editNotesPage/EditNotesPage'
5 | import SummaryPage from './summaryPage/SummaryPage'
6 | import Footer from './Footer'
7 |
8 | import '../styles/App.css'
9 |
10 | class App extends React.Component {
11 |
12 |
13 | render() {
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 | )
23 | }
24 | }
25 |
26 | export default App
27 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import '../styles/Footer.css'
3 |
4 | class Footer extends React.Component {
5 | render() {
6 | return (
7 |
8 | MSD Code Academy 2017
9 |
10 | )
11 | }
12 | }
13 |
14 | export default Footer
15 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 |
4 | import NewNoteModal from './NewNoteModal'
5 | import logo from '../logo.png'
6 | import '../styles/Header.css'
7 |
8 | class Header extends React.Component {
9 |
10 | getNoteCountMessage(noteCount){
11 | const isPlural = noteCount > 1;
12 | return `(containing ${noteCount} idea${isPlural ? 's' : ''})`
13 | }
14 |
15 | render() {
16 | const { ...props } = this.props
17 | return (
18 |
19 |
20 |

21 |
IDEA JOURNAL
22 |
List Notes
23 |
Summary
24 |
25 |
26 |
27 |
28 | )
29 | }
30 | }
31 |
32 | export default Header
33 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/NewNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import { connect } from 'react-redux'
4 |
5 | import * as shortId from 'shortid'
6 | import '../styles/NewNoteModal.css'
7 | import { actionTypes } from '../Reducer'
8 |
9 |
10 | class NewNoteModal extends React.Component {
11 | constructor() {
12 | super()
13 | this.state = {
14 | modalIsOpen: false,
15 | note: {},
16 | }
17 | }
18 |
19 | toggleModal = () => {
20 | this.setState({
21 | modalIsOpen: !this.state.modalIsOpen,
22 | })
23 | }
24 |
25 | handleChange = field => e => {
26 | let note = this.state.note
27 | note[field] = e.target.value
28 | this.setState({ note })
29 | }
30 |
31 | handleFormSubmit = e => {
32 | e.preventDefault()
33 | const { onAddNote } = this.props
34 | const { note } = this.state
35 | const newNote = { ...note, uuid: shortId.generate() }
36 | this.props.dispatch({ type: actionTypes.NOTE_ADDED, newNote })
37 | this.toggleModal()
38 | }
39 |
40 | render() {
41 | const { modalIsOpen } = this.state
42 | return (
43 |
44 |
ADD NOTE
45 |
51 | Add a new note
52 |
63 |
64 |
65 | )
66 | }
67 | }
68 |
69 | // we are not interested in any updates, we just want to add new notes to store
70 | function mapStateToProps(state) {
71 | return {}
72 | }
73 |
74 | export default connect(mapStateToProps)(NewNoteModal);
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/editNotesPage/EditNoteModal.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Modal from 'react-modal'
3 | import '../../styles/EditNoteModal.css'
4 |
5 | class EditNoteModal extends React.Component {
6 | constructor(props) {
7 | super(props)
8 | this.state = {
9 | modalIsOpen: false,
10 | note: {
11 | uuid: props.noteUuid,
12 | text: props.text,
13 | title: props.title,
14 | },
15 | }
16 | }
17 |
18 | toggleModal = () => {
19 | this.setState({
20 | modalIsOpen: !this.state.modalIsOpen,
21 | })
22 | }
23 |
24 | handleChange = field => e => {
25 | let note = this.state.note
26 | note[field] = e.target.value
27 | this.setState({ note })
28 | }
29 |
30 | handleFormSubmit = e => {
31 | e.preventDefault()
32 | this.props.editNote(this.state.note)
33 | this.toggleModal()
34 | }
35 |
36 | render() {
37 | const { modalIsOpen } = this.state
38 | return (
39 |
40 |
edit
41 |
47 | Edit a note
48 |
69 |
70 |
71 | )
72 | }
73 | }
74 |
75 | export default EditNoteModal
76 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/editNotesPage/EditNotesPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import NoteList from './NoteList'
3 | import { actionTypes, fetchNotesAsync } from '../../Reducer'
4 |
5 | import { connect } from 'react-redux'
6 |
7 | class EditNotesPage extends React.Component {
8 |
9 | componentWillMount() {
10 | this.props.dispatch(fetchNotesAsync());
11 | }
12 |
13 | editNote = editedNote => {
14 | this.props.dispatch({ type: actionTypes.NOTE_UPDATED, editedNote })
15 | }
16 |
17 | removeNoteFromList = deletedNoteUuid => () => {
18 | this.props.dispatch({ type: actionTypes.NOTE_DELETED, deletedNoteUuid })
19 | }
20 |
21 | render() {
22 | const notes = this.props.notes
23 | const noteCount = notes.length
24 | return (
)
28 | }
29 |
30 | }
31 |
32 | function mapStateToProps(state) {
33 | return {
34 | notes: state.notes
35 | };
36 | }
37 |
38 | export default connect(mapStateToProps)(EditNotesPage);
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/editNotesPage/Note.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import EditNoteModal from './EditNoteModal'
4 | import '../../styles/Note.css'
5 |
6 | class Note extends React.Component {
7 | state = {
8 | showMore: false,
9 | }
10 |
11 | handleShowMore = () => {
12 | this.setState({
13 | showMore: !this.state.showMore,
14 | })
15 | }
16 |
17 | render() {
18 | const { noteUuid, title, text, removeNoteFromList } = this.props
19 | const { showMore } = this.state
20 | return (
21 |
32 | )
33 | }
34 | }
35 |
36 | export default Note
37 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/editNotesPage/NoteList.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Note from './Note'
3 | import '../../styles/NoteList.css'
4 |
5 | class NoteList extends React.Component {
6 | render() {
7 | const { notes, editNote, removeNoteFromList } = this.props
8 | return (
9 |
10 | {notes.map(note => (
11 |
19 | ))}
20 |
21 | )
22 | }
23 | }
24 |
25 | export default NoteList
26 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/components/summaryPage/SummaryPage.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect } from 'react-redux'
3 |
4 | import '../../styles/SummaryPage.css'
5 | import { fetchNotesAsync } from '../../Reducer';
6 |
7 | class SummaryPage extends React.Component {
8 | componentWillMount() {
9 | this.props.dispatch(fetchNotesAsync());
10 | }
11 |
12 | render() {
13 | return (
14 |
15 | Number of notes: {this.props.notes.length}
16 |
17 | )
18 | }
19 |
20 | }
21 |
22 | function mapStateToProps(state) {
23 | return {
24 | notes: state.notes
25 | }
26 | }
27 |
28 | export default connect(mapStateToProps)(SummaryPage);
--------------------------------------------------------------------------------
/lesson-7/todos/src/config/api.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | BACKEND_URL: `http://localhost:3000`,
3 | }
--------------------------------------------------------------------------------
/lesson-7/todos/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { BrowserRouter } from 'react-router-dom'
4 |
5 | import { createStore, applyMiddleware } from 'redux'
6 | import { Provider } from "react-redux";
7 | import logger from 'redux-logger'
8 |
9 | import './styles/index.css';
10 | import App from './components/App';
11 | import registerServiceWorker from './registerServiceWorker';
12 |
13 | import Reducer from './Reducer';
14 |
15 | import thunk from 'redux-thunk';
16 |
17 | let store = createStore(Reducer,
18 | applyMiddleware(logger),
19 | applyMiddleware(thunk)
20 | )
21 |
22 | ReactDOM.render(
23 |
24 |
25 |
26 |
27 | , document.getElementById('root'));
28 | registerServiceWorker();
29 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-7/todos/src/logo.png
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | color: #342e24;
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: space-between;
6 | min-height: 100vh;
7 | }
8 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/EditNoteModal.css:
--------------------------------------------------------------------------------
1 | .EditNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .EditNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .EditNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .EditNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .EditNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .EditNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .EditNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .EditNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .EditNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .EditNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .EditNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .EditNoteModal-modal-window input[type="text"],
76 | .EditNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/Footer.css:
--------------------------------------------------------------------------------
1 | .Footer {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | height: 60px;
6 | width:100%;
7 | background-color: #ede0b0;
8 | box-shadow: 0 0px 8px #000000;
9 | }
10 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/Header.css:
--------------------------------------------------------------------------------
1 | .Header {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: space-between;
5 | background-color: #ede0b0;
6 | box-shadow: 0 0px 8px #000000;
7 | }
8 |
9 | .Header-logo img {
10 | height: 80px;
11 | vertical-align: middle;
12 | }
13 |
14 |
15 | .Header-logo span {
16 | margin: 10px
17 | }
18 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/NewNoteModal.css:
--------------------------------------------------------------------------------
1 | .NewNoteModal {
2 | display: flex;
3 | align-items: center;
4 | padding-right: 20px;
5 | }
6 |
7 | .NewNoteModal-modal-overlay {
8 | position: fixed;
9 | top: 0;
10 | left: 0;
11 | right: 0;
12 | bottom: 0;
13 | background-color: rgba(0, 0, 0, 0.7);
14 | z-index: 20;
15 | display: flex;
16 | align-items: center;
17 | justify-content: center;
18 | animation: reveal 200ms ease-out;
19 | }
20 |
21 | .NewNoteModal-modal-window {
22 | width: 400px;
23 | height: 300px;
24 | max-height: 98vh;
25 | border: 1px solid #ccc;
26 | background: #fff;
27 | overflow: auto;
28 | border-radius: 4px;
29 | outline: none;
30 | padding: 20px 30px 30px 30px;
31 | }
32 |
33 | .NewNoteModal a {
34 | cursor: pointer;
35 | color: #880000;
36 | }
37 |
38 | .NewNoteModal a:hover {
39 | color: #EE5757;
40 | }
41 |
42 | .NewNoteModal-modal-window div {
43 | padding-bottom: 20px;
44 | }
45 |
46 | .NewNoteModal-modal-window label {
47 | padding-right: 10px;
48 | }
49 |
50 | .NewNoteModal-modal-window input {
51 | width: 80%;
52 | font-size: 24px;
53 | }
54 |
55 | .NewNoteModal-modal-window textarea {
56 | width: 80%;
57 | font-size: 24px;
58 | resize: none;
59 | }
60 |
61 | .NewNoteModal-modal-window button {
62 | border: none;
63 | border-radius: 0px;
64 | color: #ffffff;
65 | font-size: 20px;
66 | background: #1a1a1a;
67 | padding: 9px 20px 10px 20px;
68 | text-decoration: none;
69 | }
70 |
71 | .NewNoteModal-textarea * {
72 | vertical-align: top;
73 | }
74 |
75 | .NewNoteModal-modal-window input[type="text"],
76 | .NewNoteModal-modal-window textarea {
77 | appearance: textfield;
78 | }
79 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/Note.css:
--------------------------------------------------------------------------------
1 | .Note {
2 | border-radius: 5px;
3 | border: 1px solid #DDDDDD;
4 | width: 50%;
5 | min-height: 120px;
6 | margin: 10px;
7 | padding: 20px 40px;
8 | box-shadow: 2px 2px 8px #000000;
9 | word-wrap: break-word;
10 | }
11 |
12 | .Note a {
13 | cursor: pointer;
14 | color: #880000;
15 | font-size: 14px;
16 | }
17 |
18 | .Note a:hover {
19 | color: #EE5757;
20 | }
21 |
22 | .Note-title {
23 | display: flex;
24 | justify-content: space-between;
25 | align-items: center;
26 | }
27 |
28 | .Note-text {
29 | padding-bottom: 10px;
30 | }
31 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/NoteList.css:
--------------------------------------------------------------------------------
1 | .NoteList {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/SummaryPage.css:
--------------------------------------------------------------------------------
1 | .Summary {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-7/todos/src/styles/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: Arial;
5 | }
6 |
--------------------------------------------------------------------------------
/lesson-8/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-8/README.md
--------------------------------------------------------------------------------
/lesson-9/README.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/msd-code-academy/lessons/212bb14505cf4fdfd3f56b05f727fde113e509a8/lesson-9/README.md
--------------------------------------------------------------------------------