├── Code
├── 10th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.js
│ │ ├── components
│ │ │ ├── PureComponent.js
│ │ │ ├── StatefullComponent.js
│ │ │ └── StatelessComponent.js
│ │ └── index.js
│ └── yarn.lock
├── 11th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.js
│ │ ├── components
│ │ │ └── Post.js
│ │ ├── data
│ │ │ └── posts.js
│ │ ├── index.js
│ │ └── style
│ │ │ └── Post.css
│ └── yarn.lock
├── 12th_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
├── 13th_challenge
│ ├── index.html
│ └── solution.js
├── 14th_challenge
│ ├── .env
│ ├── .env.example
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── assets
│ │ │ └── bg.gif
│ │ ├── classes
│ │ │ └── Task.js
│ │ ├── components
│ │ │ ├── AddTask.js
│ │ │ ├── Header.js
│ │ │ ├── TaskComponent.js
│ │ │ └── Tasks.js
│ │ ├── helpers
│ │ │ └── Helper.js
│ │ ├── index.js
│ │ └── services
│ │ │ └── task.service.js
│ └── yarn.lock
├── 15th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── components
│ │ │ ├── Entry.js
│ │ │ ├── Post.js
│ │ │ └── Posts.js
│ │ ├── index.js
│ │ ├── layouts
│ │ │ └── Nav.js
│ │ └── pages
│ │ │ └── NotFound.js
│ └── yarn.lock
├── 16th_challenge
│ ├── app.js
│ └── index.html
├── 17th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── components
│ │ │ ├── Post.js
│ │ │ └── User.js
│ │ ├── containers
│ │ │ ├── App.js
│ │ │ └── Root.js
│ │ ├── data
│ │ │ └── data.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── layouts
│ │ │ └── Nav.js
│ │ ├── pages
│ │ │ ├── Home.js
│ │ │ ├── NotFound.js
│ │ │ ├── Posts.js
│ │ │ ├── Users.js
│ │ │ └── index.js
│ │ └── redux
│ │ │ ├── actionCreators.js
│ │ │ ├── configureStore.js
│ │ │ ├── postReducer.js
│ │ │ ├── rootReducer.js
│ │ │ ├── types.js
│ │ │ └── userReducer.js
│ └── yarn.lock
├── 18th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── containers
│ │ │ ├── App.js
│ │ │ └── Root.js
│ │ ├── index.js
│ │ ├── pages
│ │ │ └── Home.js
│ │ └── redux
│ │ │ ├── actionCreators.js
│ │ │ ├── apiMiddleware.js
│ │ │ ├── configureStore.js
│ │ │ ├── currentTime.js
│ │ │ ├── rootReducer.js
│ │ │ ├── testMiddleware.js
│ │ │ └── types.js
│ └── yarn.lock
├── 19th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── components
│ │ │ └── App
│ │ │ │ ├── App.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ └── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ └── yarn.lock
├── 1st_challenge
│ ├── challenge.js
│ └── solution.js
├── 20th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── components
│ │ │ └── App
│ │ │ │ ├── App.js
│ │ │ │ └── index.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ └── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ └── yarn.lock
├── 21th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── App.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── Home.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ └── index.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 22th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── App.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ ├── SignInWidget.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 23th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── App.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ ├── SignInWidget.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── services
│ │ │ ├── Api.js
│ │ │ └── hackerNewsApi.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 24th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── App.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ ├── SignInWidget.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── services
│ │ │ ├── Api.js
│ │ │ └── hackerNewsApi.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ ├── story
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 25th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ ├── SignInWidget.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── services
│ │ │ ├── Api.js
│ │ │ └── hackerNewsApi.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ ├── story
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 26th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ │ ├── index.html
│ │ └── manifest.json
│ ├── src
│ │ ├── assets
│ │ │ └── logo.png
│ │ ├── components
│ │ │ ├── App
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Home
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── List
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── ListItem
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Login
│ │ │ │ ├── Login.js
│ │ │ │ ├── SignInWidget.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ ├── Nav
│ │ │ │ ├── Nav.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ │ └── News
│ │ │ │ ├── News.js
│ │ │ │ ├── index.js
│ │ │ │ └── styles.js
│ │ ├── index.js
│ │ ├── registerServiceWorker.js
│ │ ├── services
│ │ │ ├── Api.js
│ │ │ └── hackerNewsApi.js
│ │ ├── store
│ │ │ ├── app
│ │ │ │ ├── actions.js
│ │ │ │ └── reducer.js
│ │ │ ├── index.js
│ │ │ ├── middleware.js
│ │ │ ├── reducer.js
│ │ │ ├── story
│ │ │ │ ├── actions.js
│ │ │ │ ├── reducer.js
│ │ │ │ └── selectors.js
│ │ │ └── utils
│ │ │ │ └── index.js
│ │ ├── styles
│ │ │ ├── global.js
│ │ │ └── palette.js
│ │ └── utils
│ │ │ └── index.js
│ └── yarn.lock
├── 27th_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
├── 28th_challenge
│ ├── .env
│ ├── .gitignore
│ ├── README.md
│ ├── package.json
│ ├── public
│ │ └── index.html
│ ├── src
│ │ ├── App.js
│ │ ├── User.js
│ │ ├── Users.js
│ │ ├── index.js
│ │ └── useStorage.js
│ └── yarn.lock
├── 2nd_challenge
│ ├── index.html
│ └── solution.js
├── 3rd_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
├── 4th_challenge
│ ├── index.html
│ └── solution.js
├── 5th_challenge
│ ├── index.html
│ └── solution.js
├── 6th_challenge
│ ├── index.html
│ └── solution.js
├── 7th_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
├── 8th_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
└── 9th_challenge
│ ├── challenge.js
│ ├── index.html
│ └── solution.js
├── README.md
├── img
├── flux-diagram.png
└── thanks.jpg
├── log.md
└── rules.md
/Code/10th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/10th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/10th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "10th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-scripts": "2.1.5"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/Code/10th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Pure Components
6 |
7 |
8 | You need to enable JavaScript to run this app.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Code/10th_challenge/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import PureComp from './components/PureComponent'
4 | import StatefullComponent from './components/StatefullComponent'
5 | import StatelessComponent from './components/StatelessComponent'
6 |
7 | class App extends Component {
8 |
9 | state = {
10 | date: ''
11 | }
12 |
13 | componentWillMount = () => {
14 | this.timerID = setInterval(this.tick, 1000);
15 | }
16 |
17 | componentWillUnmount = () => {
18 | clearInterval(this.timerID);
19 | }
20 |
21 | tick = () => {
22 | const date = new Date().toLocaleTimeString();
23 | this.setState({date});
24 | }
25 |
26 | render() {
27 |
28 | const {date} = this.state;
29 |
30 | return (
31 |
32 |
33 |
34 |
35 |
{date}
36 |
37 | );
38 | }
39 | }
40 |
41 | export default App;
42 |
--------------------------------------------------------------------------------
/Code/10th_challenge/src/components/PureComponent.js:
--------------------------------------------------------------------------------
1 | import React, { PureComponent } from 'react';
2 |
3 | class PureComp extends PureComponent {
4 |
5 | render() {
6 |
7 | console.log('Pure Component is re rendered')
8 |
9 | return (
10 | PureComponent
11 | );
12 | }
13 | }
14 |
15 | export default PureComp;
--------------------------------------------------------------------------------
/Code/10th_challenge/src/components/StatefullComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class StatefullComponent extends Component {
4 |
5 | render() {
6 |
7 | console.log('Statefull Component is re rendered')
8 |
9 | return (
10 | StatefullComponent
11 | );
12 | }
13 | }
14 |
15 | export default StatefullComponent;
--------------------------------------------------------------------------------
/Code/10th_challenge/src/components/StatelessComponent.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const StatelessComponent = () => {
4 |
5 | console.log('Stateless Component is re rendered')
6 |
7 | return (
8 | StatelessComponent
9 | );
10 |
11 | }
12 |
13 | export default StatelessComponent;
--------------------------------------------------------------------------------
/Code/10th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/Code/11th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/11th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/11th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "10th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-scripts": "2.1.5"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/Code/11th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Lists, Keys and Conditional Rendering
6 |
7 |
8 | You need to enable JavaScript to run this app.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Code/11th_challenge/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Post from './components/Post'
4 |
5 | import './style/Post.css';
6 |
7 | import posts from './data/posts'
8 |
9 | class App extends Component {
10 |
11 | constructor(props){
12 | super(props);
13 |
14 | this.state = {
15 | posts,
16 | }
17 | }
18 |
19 | asc = () => {
20 | let {posts} = this.state;
21 |
22 | posts.sort((a, b) => {
23 | if(a.title > b.title)
24 | return 1;
25 |
26 | if(a.title < b.title)
27 | return -1;
28 |
29 | return 0;
30 | });
31 |
32 | this.setState({posts});
33 | }
34 |
35 | desc = () => {
36 | let {posts} = this.state;
37 |
38 | posts.sort((a, b) => {
39 | if(a.title < b.title)
40 | return 1;
41 |
42 | if(a.title > b.title)
43 | return -1;
44 |
45 | return 0;
46 | });
47 |
48 | this.setState({posts});
49 | }
50 |
51 | render() {
52 |
53 | const {posts} = this.state;
54 |
55 | return (
56 |
57 | {
58 | posts.map(post =>
)
59 | }
60 |
61 |
sort desc
62 |
sort asc
63 |
64 | );
65 | }
66 | }
67 |
68 | export default App;
69 |
--------------------------------------------------------------------------------
/Code/11th_challenge/src/components/Post.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Post extends Component {
4 |
5 | constructor(props){
6 | super(props);
7 |
8 | this.state = {
9 | active: false,
10 | }
11 | }
12 |
13 | toggleBody = () => {
14 | this.setState((state) => ({
15 | active: !state.active
16 | }));
17 | }
18 |
19 | render() {
20 |
21 | const {post} = this.props;
22 | const {active} = this.state;
23 |
24 | return(
25 |
26 |
{post.title}
29 | {active &&
30 |
{post.body}
31 | }
32 |
33 | );
34 | }
35 | }
36 |
37 | export default Post;
--------------------------------------------------------------------------------
/Code/11th_challenge/src/data/posts.js:
--------------------------------------------------------------------------------
1 | const posts = [{"id": 1,"title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body": "quia et suscipit suscipit recusandae consequuntur expedita et cum reprehenderit molestiae ut ut quas totam nostrum rerum est autem sunt rem eveniet architecto"},{"id": 2,"title": "qui est esse","body": "est rerum tempore vitae sequi sint nihil reprehenderit dolor beatae ea dolores neque fugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis qui aperiam non debitis possimus qui neque nisi nulla"},{"id": 3,"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut","body": "et iusto sed quo iure voluptatem occaecati omnis eligendi aut ad voluptatem doloribus vel accusantium quis pariatur molestiae porro eius odio et labore et velit aut"},{"id": 4,"title": "eum et est occaecati","body": "ullam et saepe reiciendis voluptatem adipisci sit amet autem assumenda provident rerum culpa quis hic commodi nesciunt rem tenetur doloremque ipsam iure quis sunt voluptatem rerum illo velit"},{"id": 5,"title": "nesciunt quas odio","body": "repudiandae veniam quaerat sunt sed alias aut fugiat sit autem sed est voluptatem omnis possimus esse voluptatibus quis est aut tenetur dolor neque"}];
2 |
3 | export default posts;
--------------------------------------------------------------------------------
/Code/11th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/Code/11th_challenge/src/style/Post.css:
--------------------------------------------------------------------------------
1 | .active {
2 | border-radius: 5px 5px 0 0 !important;
3 | margin: 0 !important;
4 | }
5 |
6 | .active::before {
7 | border-bottom: 5px solid #fff !important;
8 | border-top: 5px solid transparent !important;
9 | bottom: 10px !important;
10 | }
11 |
12 | .title {
13 | position: relative;
14 | background: #607D8B;
15 | text-align: center;
16 | padding: 5px;
17 | border-radius: 5px;
18 | margin: 0 0 10px 0;
19 | cursor: pointer;
20 | }
21 |
22 | .title::before {
23 | content: '';
24 | position: absolute;
25 | bottom: 5px;
26 | right: 10px;
27 | width: 0px;
28 | height: 0px;
29 | border-left: 5px solid transparent;
30 | border-right: 5px solid transparent;
31 | border-top: 5px solid #fff;
32 | border-bottom: 5px solid transparent;
33 | }
34 |
35 | .body {
36 | background: #607d8bb0;
37 | padding: 10px;
38 | border-radius: 0 0 5px 5px;
39 | margin: 0 0 5px 0;
40 | }
--------------------------------------------------------------------------------
/Code/13th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Remote Data
8 |
9 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Code/14th_challenge/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL=https://jsonplaceholder.typicode.com
2 |
3 | SKIP_PREFLIGHT_CHECK=true
4 |
--------------------------------------------------------------------------------
/Code/14th_challenge/.env.example:
--------------------------------------------------------------------------------
1 | REACT_APP_API_URL=https://jsonplaceholder.typicode.com
2 |
--------------------------------------------------------------------------------
/Code/14th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/14th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "10th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-scripts": "2.1.5",
9 | "uniqid": "^5.0.3"
10 | },
11 | "scripts": {
12 | "start": "react-scripts start",
13 | "build": "react-scripts build",
14 | "test": "react-scripts test",
15 | "eject": "react-scripts eject"
16 | },
17 | "eslintConfig": {
18 | "extends": "react-app"
19 | },
20 | "browserslist": [
21 | ">0.2%",
22 | "not dead",
23 | "not ie <= 11",
24 | "not op_mini all"
25 | ],
26 | "devDependencies": {
27 | "dotenv": "^6.2.0"
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Code/14th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | TODO APP
6 |
7 |
8 |
9 | You need to enable JavaScript to run this app.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Code/14th_challenge/src/assets/bg.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/14th_challenge/src/assets/bg.gif
--------------------------------------------------------------------------------
/Code/14th_challenge/src/classes/Task.js:
--------------------------------------------------------------------------------
1 | let uniqid = require('uniqid')
2 |
3 | class Task {
4 |
5 | constructor(description, completed = false, id = uniqid()) {
6 | this.id = id;
7 | this.description = description;
8 | this.created_at = new Date();
9 | this.completed = completed;
10 | }
11 | }
12 |
13 | export default Task;
--------------------------------------------------------------------------------
/Code/14th_challenge/src/components/Header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Header = () => {
4 |
5 | const date = new Date();
6 |
7 | return (
8 |
16 | );
17 | }
18 |
19 | export default Header;
--------------------------------------------------------------------------------
/Code/14th_challenge/src/components/Tasks.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import TaskComponent from './TaskComponent'
4 |
5 | const Tasks = (props) => {
6 |
7 | const {tasks, deleteTask, editTask, toggleTask, alert} = props;
8 |
9 | return (
10 |
11 | {!tasks.length ?
12 |
There is no tasks
13 | :
14 | tasks.map(task => {
15 | return
22 | })
23 | }
24 |
25 | );
26 | }
27 |
28 | export default Tasks;
--------------------------------------------------------------------------------
/Code/14th_challenge/src/helpers/Helper.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Generate the header
3 | * This will help better in case we have additional parameters for the header
4 | * i.e Authorization in the authentication case.
5 | */
6 | export function Header() {
7 |
8 | const header = {
9 | 'Content-Type': 'application/json; charset=UTF-8',
10 | };
11 |
12 | return header;
13 | }
14 |
15 | /**
16 | * @param response
17 | * handle the response of each request
18 | * return either the data or fire a reject
19 | */
20 | export function handleResponse(response) {
21 |
22 | return response.json().then(data => {
23 |
24 | if (!response.ok) {
25 |
26 | const { status } = response;
27 | const error = response.status === 404 ? 'Not Found' : 'There is an error';
28 |
29 | return Promise.reject({status, error});
30 | }
31 |
32 | return data;
33 |
34 | });
35 | }
--------------------------------------------------------------------------------
/Code/14th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/Code/15th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/15th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/15th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "10th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-router-dom": "^4.3.1",
9 | "react-scripts": "2.1.5",
10 | "uniqid": "^5.0.3"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test",
16 | "eject": "react-scripts eject"
17 | },
18 | "eslintConfig": {
19 | "extends": "react-app"
20 | },
21 | "browserslist": [
22 | ">0.2%",
23 | "not dead",
24 | "not ie <= 11",
25 | "not op_mini all"
26 | ],
27 | "devDependencies": {
28 | "dotenv": "^6.2.0"
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Code/15th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Router
6 |
7 |
8 |
9 | You need to enable JavaScript to run this app.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Code/15th_challenge/src/App.css:
--------------------------------------------------------------------------------
1 | * {margin: 0;padding: 0}
2 |
3 | .not-found {
4 | position: absolute;
5 | background: #fff;
6 | color: #607c8a;
7 | height: 100%;
8 | width: 100%;
9 | top: auto;
10 | display: grid;
11 | justify-content: center;
12 | align-items: center;
13 | animation: animate 1s infinite alternate;
14 | }
15 |
16 | @keyframes animate {
17 | from {color: #607c8a;}
18 | to {color: #f44336}
19 | }
20 |
21 | nav {
22 | background-color: #607D8B;
23 | padding: 10px;
24 | }
25 |
26 | nav > ul > li {
27 | list-style: none;
28 | display: inline;
29 | }
30 |
31 | nav > ul > li > a {
32 | padding: 10px;
33 | color: white;
34 | text-decoration: none;
35 | }
36 |
37 | nav > ul > li > .active {
38 | background: #fff;
39 | color: #607D8B;
40 | }
41 |
42 | .posts {
43 | display: flex;
44 | flex-direction: column;
45 | padding: 10px;
46 | }
47 |
48 | .entry {
49 | display: flex;
50 | flex-direction: column;
51 | padding: 5px;
52 | margin: 5px 0;
53 | box-shadow: 0px 1px 5px #eee;
54 | }
55 |
56 | .entry > a {
57 | align-self: flex-end;
58 | }
59 |
60 | .post {
61 | padding: 10px;
62 | display: flex;
63 | flex-direction: column;
64 | }
65 |
66 | .post > p {
67 | letter-spacing: 1px;
68 | padding: 5px 0;
69 | }
--------------------------------------------------------------------------------
/Code/15th_challenge/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
3 |
4 | import Nav from './layouts/Nav';
5 | import NotFound from './pages/NotFound';
6 | import Post from './components/Post';
7 | import Posts from './components/Posts';
8 |
9 | import './App.css'
10 |
11 | class App extends Component {
12 |
13 | render() {
14 |
15 | return (
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | );
29 | }
30 | }
31 |
32 | export default App;
--------------------------------------------------------------------------------
/Code/15th_challenge/src/components/Entry.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from "react-router-dom";
3 |
4 | const Entry = (props) => {
5 |
6 | const {id, title} = props;
7 |
8 | return (
9 |
10 |
11 |
{title}
12 | Read more
13 |
14 | );
15 | }
16 |
17 | export default Entry;
--------------------------------------------------------------------------------
/Code/15th_challenge/src/components/Post.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Route, Redirect } from "react-router-dom";
3 |
4 | class Post extends Component {
5 |
6 | constructor(props) {
7 | super(props);
8 |
9 | this.state = {
10 | error: {},
11 | loading: false,
12 | post: {},
13 | }
14 | }
15 |
16 | componentDidMount() {
17 |
18 | this.setState({loading: true});
19 |
20 | const {params: {postId}} = this.props.match;
21 |
22 | fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
23 | .then(response => {
24 |
25 | if(!response.ok) {
26 | return Promise.reject(response.status);
27 | }
28 |
29 | return response.json();
30 | })
31 | .then(post => {
32 | this.setState({post, loading: false});
33 | }).catch(status => {
34 | this.setState({error: {status}});
35 | });
36 | }
37 |
38 | render() {
39 | const {loading, post, error: {status}} = this.state;
40 |
41 | return (
42 |
43 |
44 | {status ?
45 | } />
46 | :
47 |
48 | {loading ?
49 |
Loading...
50 | :
51 |
52 | {post.title}
53 | {post.body}
54 |
55 | }
56 |
57 | }
58 |
59 | );
60 | }
61 | }
62 |
63 | export default Post;
--------------------------------------------------------------------------------
/Code/15th_challenge/src/components/Posts.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import Entry from './Entry';
4 |
5 | class Posts extends Component {
6 |
7 | constructor(props) {
8 | super(props);
9 |
10 | this.state = {
11 | loading: false,
12 | posts: [],
13 | }
14 | }
15 |
16 | componentDidMount() {
17 |
18 | this.setState({loading: true});
19 |
20 | fetch('https://jsonplaceholder.typicode.com/posts')
21 | .then(response => response.json())
22 | .then(posts => {
23 | this.setState({posts, loading: false});
24 | });
25 | }
26 |
27 | render() {
28 | const {loading, posts} = this.state;
29 |
30 | return (
31 |
32 |
33 | {loading ?
34 |
Loading...
35 | :
36 | posts.map(post => )
37 | }
38 |
39 | );
40 | }
41 |
42 | }
43 |
44 | export default Posts;
--------------------------------------------------------------------------------
/Code/15th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/Code/15th_challenge/src/layouts/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from "react-router-dom";
3 |
4 |
5 | const Nav = () => {
6 |
7 | return (
8 |
9 |
10 |
11 | Home
12 |
13 |
14 | Not Found
15 |
16 |
17 |
18 | );
19 | }
20 |
21 | export default Nav;
--------------------------------------------------------------------------------
/Code/15th_challenge/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => {
4 |
5 | return (
6 | Not Found
7 | );
8 | }
9 |
10 | export default NotFound;
--------------------------------------------------------------------------------
/Code/16th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Redux Without React
8 |
9 |
10 |
11 |
12 | Increment
13 | Decrement
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Code/17th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/17th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/17th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "10th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-redux": "^6.0.1",
9 | "react-router-dom": "^4.3.1",
10 | "react-scripts": "2.1.5",
11 | "redux": "^4.0.1",
12 | "uniqid": "^5.0.3"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": "react-app"
22 | },
23 | "browserslist": [
24 | ">0.2%",
25 | "not dead",
26 | "not ie <= 11",
27 | "not op_mini all"
28 | ],
29 | "devDependencies": {
30 | "dotenv": "^6.2.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Code/17th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Redux for react
6 |
7 |
8 |
9 | You need to enable JavaScript to run this app.
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/Code/17th_challenge/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
3 |
4 | import { Home, Users, Posts, NotFound } from '../pages';
5 | import Nav from '../layouts/Nav';
6 |
7 | const App = () => (
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export default App;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/containers/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux'
3 |
4 | import configureStore from '../redux/configureStore'
5 |
6 | import App from './App';
7 |
8 | const Root = () => {
9 |
10 | const store = configureStore();
11 |
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default Root;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Root from './containers/Root';
5 |
6 | import './index.css';
7 |
8 | ReactDOM.render( , document.getElementById('root'));
9 |
--------------------------------------------------------------------------------
/Code/17th_challenge/src/layouts/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 |
4 | const Nav = () => (
5 |
6 |
7 |
8 |
9 | Home
10 |
11 |
12 | Users
13 |
14 |
15 | Posts
16 |
17 |
18 |
19 |
20 | );
21 |
22 | export default Nav;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Home = () => (
4 | Welcome to React Redux
5 | );
6 |
7 | export default Home;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/pages/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const NotFound = () => (
4 | 404 Not Found
5 | );
6 |
7 | export default NotFound;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/pages/Posts.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { connect } from 'react-redux';
4 | import { deletePost } from '../redux/actionCreators';
5 |
6 | import Post from '../components/Post';
7 |
8 | const Posts = (props) => {
9 |
10 | const { posts, deletePost } = props;
11 |
12 | return (
13 |
14 | {posts.length === 0 ?
15 |
There are no posts
16 | :
17 | posts.map(post =>(
18 |
22 | ))
23 | }
24 |
25 | );
26 | }
27 |
28 | const mapStateToProps = state => ({
29 | posts: state.post.posts,
30 | });
31 |
32 | const mapDispatchToProps = dispatch => ({
33 | deletePost: (id) => dispatch(deletePost(id)),
34 | });
35 |
36 | export default connect(mapStateToProps, mapDispatchToProps)(Posts);
--------------------------------------------------------------------------------
/Code/17th_challenge/src/pages/Users.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { connect } from 'react-redux';
4 | import { deleteUser } from '../redux/actionCreators';
5 |
6 | import User from '../components/User';
7 |
8 | const Users = (props) => {
9 |
10 | const { users, deleteUser } = props;
11 |
12 | return (
13 |
14 | {users.length === 0 ?
15 |
There are no users
16 | :
17 | users.map(user =>(
18 |
22 | ))
23 | }
24 |
25 | );
26 | }
27 |
28 | const mapStateToProps = state => ({
29 | users: state.user.users,
30 | });
31 |
32 | const mapDispatchToProps = dispatch => ({
33 | deleteUser: (id) => dispatch(deleteUser(id)),
34 | });
35 |
36 | export default connect(mapStateToProps, mapDispatchToProps)(Users);
--------------------------------------------------------------------------------
/Code/17th_challenge/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home';
2 | import Users from './Users';
3 | import Posts from './Posts';
4 | import NotFound from './NotFound';
5 |
6 | export {
7 | Home,
8 | Users,
9 | Posts,
10 | NotFound,
11 | }
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const deleteUser = (id) => ({
4 | type: types.DELETE_USER,
5 | payload: id,
6 | });
7 |
8 | const deletePost = (id) => ({
9 | type: types.DELETE_POST,
10 | payload: id,
11 | });
12 |
13 | export {
14 | deletePost,
15 | deleteUser,
16 | }
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux'
2 |
3 | import { initialState, rootReducer } from './rootReducer'
4 |
5 | const configureStore = () => {
6 |
7 | const store = createStore(
8 | rootReducer,
9 | initialState,
10 | );
11 |
12 | return store;
13 |
14 | }
15 |
16 | export default configureStore;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/postReducer.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | import { Posts } from '../data/data';
4 |
5 | export const initialState = {
6 | posts: Posts,
7 | }
8 |
9 | export const postReducer = (state = initialState, action) => {
10 |
11 | switch(action.type) {
12 | case types.DELETE_POST:
13 | const posts = state.posts.filter(post => post.id !== action.payload);
14 | return {...state, posts};
15 | default:
16 | return state;
17 | }
18 | }
19 |
20 | export default postReducer;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux'
2 |
3 | import {initialState as userInitialState, userReducer} from './userReducer';
4 | import {initialState as postInitialState, postReducer} from './postReducer';
5 |
6 |
7 | // Combine all the initial state into one root state
8 | export const initialState = {
9 | user: userInitialState,
10 | post: postInitialState,
11 | }
12 |
13 | // Combine my reducer into one single root reducer
14 | export const rootReducer = combineReducers({
15 | user: userReducer,
16 | post: postReducer,
17 | });
18 |
19 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/types.js:
--------------------------------------------------------------------------------
1 | export const DELETE_USER = 'DELETE_USER';
2 |
3 | export const DELETE_POST = 'DELETE_POST';
--------------------------------------------------------------------------------
/Code/17th_challenge/src/redux/userReducer.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | import { Users } from '../data/data';
4 |
5 | export const initialState = {
6 | users: Users,
7 | }
8 |
9 | export const userReducer = (state = initialState, action) => {
10 |
11 | switch(action.type) {
12 | case types.DELETE_USER:
13 | const users = state.users.filter(user => user.id !== action.payload);
14 | return {...state, users};
15 | default:
16 | return state;
17 | }
18 | }
19 |
20 | export default userReducer;
--------------------------------------------------------------------------------
/Code/18th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/18th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/18th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "17th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.3",
7 | "react-dom": "^16.8.3",
8 | "react-redux": "^6.0.1",
9 | "react-router-dom": "^4.3.1",
10 | "react-scripts": "2.1.5",
11 | "redux": "^4.0.1",
12 | "uniqid": "^5.0.3"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test",
18 | "eject": "react-scripts eject"
19 | },
20 | "eslintConfig": {
21 | "extends": "react-app"
22 | },
23 | "browserslist": [
24 | ">0.2%",
25 | "not dead",
26 | "not ie <= 11",
27 | "not op_mini all"
28 | ],
29 | "devDependencies": {
30 | "dotenv": "^6.2.0"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Code/18th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | React Redux | Middleware
6 |
7 |
8 | You need to enable JavaScript to run this app.
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/Code/18th_challenge/src/containers/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Home from '../pages/Home';
4 |
5 | const App = (props) => {
6 | return (
7 |
8 | )
9 | }
10 |
11 | export default App;
--------------------------------------------------------------------------------
/Code/18th_challenge/src/containers/Root.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux'
3 |
4 | import configureStore from '../redux/configureStore'
5 |
6 | import App from './App';
7 |
8 | const Root = () => {
9 |
10 | const store = configureStore();
11 |
12 | return (
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | export default Root;
--------------------------------------------------------------------------------
/Code/18th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 |
4 | import Root from './containers/Root';
5 |
6 | ReactDOM.render( , document.getElementById('root'));
7 |
--------------------------------------------------------------------------------
/Code/18th_challenge/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { fetchNewTime } from '../redux/actionCreators';
5 |
6 | const Home = (props) => {
7 | return (
8 |
9 |
Welcome home!
10 |
Current time: {props.currentTime}
11 |
12 | Update time
13 |
14 |
15 | );
16 | }
17 |
18 | const mapStateToProps = state => {
19 | return {
20 | currentTime: state.currentTime.currentTime
21 | }
22 | }
23 |
24 | const mapDispatchToProps = dispatch => ({
25 | updateTime: (opts={}) => dispatch(fetchNewTime(opts))
26 | })
27 |
28 | export default connect(
29 | mapStateToProps,
30 | mapDispatchToProps,
31 | )(Home);
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/actionCreators.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | const host = 'https://andthetimeis.com';
4 |
5 | export const fetchNewTime = ({ timezone = 'gmt', str='now'}) => ({
6 | type: types.FETCH_NEW_TIME,
7 | payload: new Date().toString(),
8 | meta: {
9 | type: 'api',
10 | url: host + '/' + timezone + '/' + str + '.json'
11 | }
12 | });
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/apiMiddleware.js:
--------------------------------------------------------------------------------
1 | const apiMiddleware = store => next => action => {
2 |
3 | console.log(`Log from api Middleware`, action);
4 |
5 | if (!action.meta || action.meta.type !== 'api') {
6 | return next(action);
7 | }
8 |
9 | // If the action object contains meta data we'll proceed to the api call
10 | // Find the request URL and compose request options from meta
11 | const {url} = action.meta;
12 | const fetchOptions = Object.assign({}, action.meta);
13 |
14 | // Make the request
15 | fetch(url, fetchOptions)
16 | // convert the response to json
17 | .then(resp => resp.json())
18 | .then(json => {
19 | // respond back to the user
20 | // by dispatching the original action without the meta object
21 | let newAction = Object.assign({}, action, {
22 | // we can return the response directly, but i prefer parse it to javascript date object
23 | payload: new Date(json.dateString).toString()
24 | });
25 |
26 | next(newAction);
27 | });
28 | }
29 |
30 | export default apiMiddleware
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/configureStore.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import { rootReducer, initialState } from './rootReducer'
3 |
4 | import apiMiddleware from './apiMiddleware';
5 | import testMiddleware from './testMiddleware';
6 |
7 | export const configureStore = () => {
8 | const store = createStore(
9 | rootReducer,
10 | initialState,
11 | applyMiddleware(
12 | apiMiddleware,
13 | testMiddleware,
14 | )
15 | );
16 |
17 | return store;
18 | }
19 |
20 | export default configureStore;
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/currentTime.js:
--------------------------------------------------------------------------------
1 | import * as types from './types';
2 |
3 | export const initialState = {
4 | currentTime: new Date().toString(),
5 | }
6 |
7 | export const reducer = (state = initialState, action) => {
8 | switch(action.type) {
9 | case types.FETCH_NEW_TIME:
10 | return { ...state, currentTime: action.payload}
11 | default:
12 | return state;
13 | }
14 | }
15 |
16 | export default reducer
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/rootReducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import * as currentTime from './currentTime';
4 |
5 | export const rootReducer = combineReducers({
6 | currentTime: currentTime.reducer,
7 | })
8 |
9 | export const initialState = {
10 | currentTime: currentTime.initialState,
11 | }
12 |
13 | export default rootReducer
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/testMiddleware.js:
--------------------------------------------------------------------------------
1 | const testMiddleware = (store) => (next) => (action) => {
2 | // Our middleware
3 | console.log(`Log from test Middleware:`, action)
4 | // call the next function
5 | next(action);
6 | }
7 |
8 | export default testMiddleware
--------------------------------------------------------------------------------
/Code/18th_challenge/src/redux/types.js:
--------------------------------------------------------------------------------
1 | export const FETCH_NEW_TIME = 'FETCH_NEW_TIME';
--------------------------------------------------------------------------------
/Code/19th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/19th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/19th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/19th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Code/19th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/19th_challenge/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 |
5 | render() {
6 |
7 | return (
8 | Welcome to Hacker News Clone
9 | );
10 | }
11 | }
12 |
13 | export default App;
--------------------------------------------------------------------------------
/Code/19th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/19th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import GlobalStyle from 'styles/global';
6 | import App from 'components/App'
7 |
8 | const renderApp = () => {
9 |
10 | ReactDOM.render(
11 |
12 |
13 |
14 |
15 | , document.getElementById('hn-root'));
16 | }
17 |
18 | renderApp();
19 | registerServiceWorker();
20 |
--------------------------------------------------------------------------------
/Code/19th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 | * {
5 | box-sizing: border-box;
6 | }
7 | html, body {
8 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
9 | width: 100vw;
10 | overflow-x: hidden;
11 | margin: 0;
12 | padding: 0;
13 | }
14 | ul {
15 | list-style: none;
16 | padding: 0;
17 | }
18 | a {
19 | text-decoration: none;
20 | &:visited {
21 | color: inherit;
22 | }
23 | }
24 | `;
25 |
26 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/19th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/1st_challenge/challenge.js:
--------------------------------------------------------------------------------
1 | class MyComponent extends React.Component {
2 |
3 | constructor() {
4 | super();
5 | this.state = {
6 | names: ['name 1', 'name 2', 'name 3'],
7 | }
8 | }
9 |
10 | addName = () => {
11 | // this function is correct
12 | const name = document.querySelector("#input").value;
13 | if(name.length === 0){
14 | alert("insert a valid name");
15 | return;
16 | }
17 | let names = this.state.names;
18 | names.push(name);
19 | this.setState({names})
20 | }
21 |
22 | render () {
23 | const {names} = this.state;
24 | const items = [];
25 | return (
26 | Name:
27 |
28 | Add
29 |
30 | {
31 | for (const [index, value] of names.entries()) {
32 | items.push({value} )
33 | }
34 | }
35 |
36 | );
37 | }
38 | }
39 |
40 | ReactDOM.render( , document.getElementById('root'));
41 |
42 |
43 |
--------------------------------------------------------------------------------
/Code/1st_challenge/solution.js:
--------------------------------------------------------------------------------
1 | class MyComponent extends React.Component {
2 |
3 | constructor() {
4 | super();
5 | this.state = {
6 | names: ['name 1', 'name 2', 'name 3'],
7 | }
8 | }
9 |
10 | addName = () => {
11 | // this function is correct
12 | const name = document.querySelector("#input").value;
13 | if(name.length === 0){
14 | alert("insert a valid name");
15 | return;
16 | }
17 | let names = this.state.names;
18 | names.push(name);
19 | this.setState({names})
20 | }
21 |
22 | render () {
23 | const {names} = this.state;
24 | const items = [];
25 | for (const [index, value] of names.entries()) {
26 | items.push({value} )
27 | }
28 | return (
29 |
30 |
Name:
31 |
32 |
Add
33 |
34 |
37 |
38 | );
39 | }
40 | }
41 |
42 | ReactDOM.render( , document.getElementById('root'));
43 |
44 |
45 |
--------------------------------------------------------------------------------
/Code/20th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/20th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/20th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/20th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Code/20th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/20th_challenge/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class App extends Component {
4 |
5 | render() {
6 |
7 | return (
8 | Welcome to Hacker News Clone
9 | );
10 | }
11 | }
12 |
13 | export default App;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App'
10 |
11 | const renderApp = () => {
12 | const initialState = {};
13 | const store = configureStore(initialState);
14 |
15 | // Init dispatch to create the store
16 | // check the console to see the output of redux logger
17 | store.dispatch({type: '@hnClone/@@INIT'});
18 |
19 | ReactDOM.render(
20 |
21 |
22 |
23 |
24 | , document.getElementById('hn-root'));
25 | }
26 |
27 | renderApp();
28 | registerServiceWorker();
29 |
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 |
5 | // The name of each reducer will be the key
6 | const rootReducer = combineReducers({
7 | app,
8 | });
9 |
10 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
--------------------------------------------------------------------------------
/Code/20th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 |
3 | const GlobalStyle = createGlobalStyle`
4 | * {
5 | box-sizing: border-box;
6 | }
7 | html, body {
8 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
9 | width: 100vw;
10 | overflow-x: hidden;
11 | margin: 0;
12 | padding: 0;
13 | }
14 | ul {
15 | list-style: none;
16 | padding: 0;
17 | }
18 | a {
19 | text-decoration: none;
20 | &:visited {
21 | color: inherit;
22 | }
23 | }
24 | `;
25 |
26 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/20th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/21th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/21th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/21th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/21th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/21th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
4 | import { ThemeProvider } from 'styled-components';
5 |
6 | import Nav from 'components/Nav';
7 | import News from 'components/News';
8 | import Home from 'components/Home';
9 | import Login from 'components/Login';
10 |
11 | import { colorsDark } from 'styles/palette';
12 | import { Wrapper } from './styles';
13 |
14 | class App extends Component {
15 |
16 | render() {
17 |
18 | return (
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 | );
34 | }
35 | }
36 |
37 | export default App;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: 100vh;
8 | overflow: hidden;
9 | position: relative;
10 | `;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import Home from './Home';
2 |
3 | export default Home;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default List;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
6 |
7 | const ListItem = () => (
8 | -
9 |
10 |
11 | Facebook Developer Circle (facebook.com)
12 |
13 |
14 |
15 | 80000 points by{' '}
16 |
17 | ilyasse benrkia
18 | {' '}
19 | 1 Hour Ago{' | '}
20 |
21 | 200 Comments
22 |
23 |
24 |
25 | );
26 |
27 | export default ListItem;
28 |
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Login/Login.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Login = () => (
4 | Login Ui goes here
5 | );
6 |
7 | export default Login;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 |
3 | export default Login;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Nav/Nav.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { Link } from 'react-router-dom';
4 |
5 | import { Header, Spacer, NavSection, NavItem, Content } from './styles';
6 |
7 | const Nav = () => (
8 |
9 |
10 |
11 |
12 |
13 | Home
14 |
15 |
16 | News
17 |
18 |
19 | Login
20 |
21 |
22 |
23 |
24 |
25 |
26 | );
27 |
28 | export default Nav;
29 |
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 |
3 | export default Nav;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | `;
13 |
14 | export const Content = styled.div`
15 | height: 100%;
16 | width: 100%;
17 | max-width: 85%;
18 | margin-left: auto;
19 | margin-right: auto;
20 | display: flex;
21 | justify-content: space-between;
22 | align-items: center;
23 | `;
24 |
25 | export const NavSection = styled.ul`
26 | display: flex;
27 | align-items: center;
28 | `;
29 |
30 | export const NavItem = styled.li`
31 | line-height: ${HEIGHT}px;
32 | padding: 0 10px;
33 | `;
34 |
35 | export const Spacer = styled.div`
36 | height: ${HEIGHT}px;
37 | `;
38 |
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/News/News.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import List from 'components/List';
4 |
5 | import { Title } from './styles'
6 |
7 | const News = () => (
8 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 | );
15 |
16 | export default News;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import News from './News';
2 |
3 | export default News;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App'
10 |
11 | const renderApp = () => {
12 | const initialState = {};
13 | const store = configureStore(initialState);
14 |
15 | // Init dispatch to create the store
16 | // check the console to see the output of redux logger
17 | store.dispatch({type: '@hnClone/@@INIT'});
18 |
19 | ReactDOM.render(
20 |
21 |
22 |
23 |
24 | , document.getElementById('hn-root'));
25 | }
26 |
27 | renderApp();
28 | registerServiceWorker();
29 |
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 |
5 | // The name of each reducer will be the key
6 | const rootReducer = combineReducers({
7 | app,
8 | });
9 |
10 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
--------------------------------------------------------------------------------
/Code/21th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/21th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/21th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const LINK_REL = "noopener noreferrer nofollow";
--------------------------------------------------------------------------------
/Code/22th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/22th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/22th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/22th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/22th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/22th_challenge/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/22th_challenge/src/assets/logo.png
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: 100vh;
8 | overflow: hidden;
9 | position: relative;
10 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default List;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
6 |
7 | const ListItem = () => (
8 | -
9 |
10 |
11 | Facebook Developer Circle (facebook.com)
12 |
13 |
14 |
15 | 80000 points by{' '}
16 |
17 | ilyasse benrkia
18 | {' '}
19 | 1 Hour Ago{' | '}
20 |
21 | 200 Comments
22 |
23 |
24 |
25 | );
26 |
27 | export default ListItem;
28 |
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Login/SignInWidget.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OktaSignIn from '@okta/okta-signin-widget';
4 | import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
5 | import '@okta/okta-signin-widget/dist/css/okta-theme.css';
6 |
7 | import { Wrapper } from './styles';
8 |
9 | import logo from 'assets/logo.png';
10 |
11 | // This is a simple widget created by octa
12 | // We can customize it using our own css
13 | class SignInWidget extends Component {
14 |
15 | componentDidMount() {
16 | const el = ReactDOM.findDOMNode(this);
17 | this.widget = new OktaSignIn({
18 | baseUrl: this.props.baseUrl,
19 | logo: logo,
20 | });
21 | this.widget.renderEl({ el }, this.props.onSuccess, this.props.onError);
22 | }
23 |
24 | componentWillUnmount() {
25 | this.widget.remove();
26 | }
27 |
28 | render() {
29 | return
30 | }
31 | }
32 |
33 | export default SignInWidget;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Login);
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Login/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | display: flex;
5 | height: 100%;
6 | align-items: center;
7 | justify-content: center;
8 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Nav);
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | z-index: 9999;
13 | `;
14 |
15 | export const Content = styled.div`
16 | height: 100%;
17 | width: 100%;
18 | max-width: 85%;
19 | margin-left: auto;
20 | margin-right: auto;
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | `;
25 |
26 | export const NavSection = styled.ul`
27 | display: flex;
28 | align-items: center;
29 | `;
30 |
31 | export const NavItem = styled.li`
32 | line-height: ${HEIGHT}px;
33 | padding: 0 10px;
34 |
35 | & > button {
36 | cursor: pointer;
37 | background-color: transparent;
38 | border: none;
39 | color: inherit;
40 | font-size: 16px;
41 | }
42 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/News/News.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import List from 'components/List';
4 |
5 | import { Title } from './styles'
6 |
7 | const News = () => (
8 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 | );
15 |
16 | export default News;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import News from './News';
2 |
3 | export default News;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App'
10 |
11 | const renderApp = () => {
12 | const initialState = {};
13 | const store = configureStore(initialState);
14 |
15 | // Init dispatch to create the store
16 | // check the console to see the output of redux logger
17 | store.dispatch({type: '@hnClone/@@INIT'});
18 |
19 | ReactDOM.render(
20 |
21 |
22 |
23 |
24 | , document.getElementById('hn-root'));
25 | }
26 |
27 | renderApp();
28 | registerServiceWorker();
29 |
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 |
5 | // The name of each reducer will be the key
6 | const rootReducer = combineReducers({
7 | app,
8 | });
9 |
10 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
--------------------------------------------------------------------------------
/Code/22th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/22th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/22th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const LINK_REL = "noopener noreferrer nofollow";
2 |
3 | export const onAuthRequired = ({ history }) => {
4 | history.push('/login');
5 | }
--------------------------------------------------------------------------------
/Code/23th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/23th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/23th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/23th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/23th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/23th_challenge/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/23th_challenge/src/assets/logo.png
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: 100vh;
8 | overflow: hidden;
9 | position: relative;
10 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default List;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
6 |
7 | const ListItem = () => (
8 | -
9 |
10 |
11 | Facebook Developer Circle (facebook.com)
12 |
13 |
14 |
15 | 80000 points by{' '}
16 |
17 | ilyasse benrkia
18 | {' '}
19 | 1 Hour Ago{' | '}
20 |
21 | 200 Comments
22 |
23 |
24 |
25 | );
26 |
27 | export default ListItem;
28 |
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Login/SignInWidget.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OktaSignIn from '@okta/okta-signin-widget';
4 | import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
5 | import '@okta/okta-signin-widget/dist/css/okta-theme.css';
6 |
7 | import { Wrapper } from './styles';
8 |
9 | import logo from 'assets/logo.png';
10 |
11 | // This is a simple widget created by octa
12 | // We can customize it using our own css
13 | class SignInWidget extends Component {
14 |
15 | componentDidMount() {
16 | const el = ReactDOM.findDOMNode(this);
17 | this.widget = new OktaSignIn({
18 | baseUrl: this.props.baseUrl,
19 | logo: logo,
20 | });
21 | this.widget.renderEl({ el }, this.props.onSuccess, this.props.onError);
22 | }
23 |
24 | componentWillUnmount() {
25 | this.widget.remove();
26 | }
27 |
28 | render() {
29 | return
30 | }
31 | }
32 |
33 | export default SignInWidget;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Login);
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Login/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | display: flex;
5 | height: 100%;
6 | align-items: center;
7 | justify-content: center;
8 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Nav);
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | z-index: 9999;
13 | `;
14 |
15 | export const Content = styled.div`
16 | height: 100%;
17 | width: 100%;
18 | max-width: 85%;
19 | margin-left: auto;
20 | margin-right: auto;
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | `;
25 |
26 | export const NavSection = styled.ul`
27 | display: flex;
28 | align-items: center;
29 | `;
30 |
31 | export const NavItem = styled.li`
32 | line-height: ${HEIGHT}px;
33 | padding: 0 10px;
34 |
35 | & > button {
36 | cursor: pointer;
37 | background-color: transparent;
38 | border: none;
39 | color: inherit;
40 | font-size: 16px;
41 | }
42 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/News/News.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import List from 'components/List';
4 |
5 | import { Title } from './styles'
6 |
7 | const News = () => (
8 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 | );
15 |
16 | export default News;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import News from './News';
2 |
3 | export default News;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App'
10 |
11 | import hackerNewsApi from 'services/hackerNewsApi';
12 |
13 | const renderApp = () => {
14 |
15 | // A simple test of the hackerNews Api
16 | hackerNewsApi.getTopStoryIds()
17 | .then(ids => console.log(ids));
18 |
19 | const initialState = {};
20 | const store = configureStore(initialState);
21 |
22 | // Init dispatch to create the store
23 | // check the console to see the output of redux logger
24 | store.dispatch({type: '@hnClone/@@INIT'});
25 |
26 | ReactDOM.render(
27 |
28 |
29 |
30 |
31 | , document.getElementById('hn-root'));
32 | }
33 |
34 | renderApp();
35 | registerServiceWorker();
36 |
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 |
5 | // The name of each reducer will be the key
6 | const rootReducer = combineReducers({
7 | app,
8 | });
9 |
10 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
--------------------------------------------------------------------------------
/Code/23th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/23th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/23th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const LINK_REL = "noopener noreferrer nofollow";
2 |
3 | export const onAuthRequired = ({ history }) => {
4 | history.push('/login');
5 | }
--------------------------------------------------------------------------------
/Code/24th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/24th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/24th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/24th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/24th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/24th_challenge/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/24th_challenge/src/assets/logo.png
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/App/index.js:
--------------------------------------------------------------------------------
1 | import App from './App';
2 |
3 | // This file is useless for the moment but we'll need it in the next day
4 |
5 | export default App;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: 100vh;
8 | overflow: hidden;
9 | position: relative;
10 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = () => (
7 |
8 |
9 |
10 | );
11 |
12 | export default List;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
6 |
7 | const ListItem = () => (
8 | -
9 |
10 |
11 | Facebook Developer Circle (facebook.com)
12 |
13 |
14 |
15 | 80000 points by{' '}
16 |
17 | ilyasse benrkia
18 | {' '}
19 | 1 Hour Ago{' | '}
20 |
21 | 200 Comments
22 |
23 |
24 |
25 | );
26 |
27 | export default ListItem;
28 |
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Login/SignInWidget.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OktaSignIn from '@okta/okta-signin-widget';
4 | import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
5 | import '@okta/okta-signin-widget/dist/css/okta-theme.css';
6 |
7 | import { Wrapper } from './styles';
8 |
9 | import logo from 'assets/logo.png';
10 |
11 | // This is a simple widget created by octa
12 | // We can customize it using our own css
13 | class SignInWidget extends Component {
14 |
15 | componentDidMount() {
16 | const el = ReactDOM.findDOMNode(this);
17 | this.widget = new OktaSignIn({
18 | baseUrl: this.props.baseUrl,
19 | logo: logo,
20 | });
21 | this.widget.renderEl({ el }, this.props.onSuccess, this.props.onError);
22 | }
23 |
24 | componentWillUnmount() {
25 | this.widget.remove();
26 | }
27 |
28 | render() {
29 | return
30 | }
31 | }
32 |
33 | export default SignInWidget;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Login);
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Login/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | display: flex;
5 | height: 100%;
6 | align-items: center;
7 | justify-content: center;
8 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Nav);
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | z-index: 9999;
13 | `;
14 |
15 | export const Content = styled.div`
16 | height: 100%;
17 | width: 100%;
18 | max-width: 85%;
19 | margin-left: auto;
20 | margin-right: auto;
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | `;
25 |
26 | export const NavSection = styled.ul`
27 | display: flex;
28 | align-items: center;
29 | `;
30 |
31 | export const NavItem = styled.li`
32 | line-height: ${HEIGHT}px;
33 | padding: 0 10px;
34 |
35 | & > button {
36 | cursor: pointer;
37 | background-color: transparent;
38 | border: none;
39 | color: inherit;
40 | font-size: 16px;
41 | }
42 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/News/News.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import List from 'components/List';
4 |
5 | import { Title } from './styles'
6 |
7 | const News = () => (
8 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 | );
15 |
16 | export default News;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import News from './News';
2 |
3 | export default News;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App';
10 |
11 | import actions from './store/story/actions';
12 |
13 | const renderApp = () => {
14 |
15 | const initialState = {};
16 | const store = configureStore(initialState);
17 |
18 | // A simple test of the story fetchStoryIds & fetchStories actions
19 | // Do not forget that fetchStoryIds call the fetchStories action in case of success.
20 | store.dispatch(actions.fetchStoryIds());
21 |
22 | ReactDOM.render(
23 |
24 |
25 |
26 |
27 | , document.getElementById('hn-root'));
28 | }
29 |
30 | renderApp();
31 | registerServiceWorker();
32 |
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 | import story from './story/reducer';
5 |
6 | // The name of each reducer will be the key
7 | const rootReducer = combineReducers({
8 | app,
9 | story,
10 | });
11 |
12 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/story/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions';
2 |
3 | const getInitialState = () => ({
4 | storyIds: [],
5 | stories: [],
6 | page: 0,
7 | isFetching: false,
8 | error: '',
9 | });
10 |
11 | const story = (state = getInitialState(), { type, payload }) => {
12 | switch (type) {
13 | case `${actionTypes.FETCH_STORY_IDS}_REQUEST`:
14 | case `${actionTypes.FETCH_STORIES}_REQUEST`:
15 | return {
16 | ...state,
17 | isFetching: true,
18 | };
19 | case `${actionTypes.FETCH_STORY_IDS}_FAILURE`:
20 | case `${actionTypes.FETCH_STORIES}_FAILURE`:
21 | return {
22 | ...state,
23 | ...payload,
24 | isFetching: false,
25 | };
26 | case `${actionTypes.FETCH_STORY_IDS}_SUCCESS`:
27 | return {
28 | ...state,
29 | ...payload,
30 | isFetching: false,
31 | };
32 | case `${actionTypes.FETCH_STORIES}_SUCCESS`:
33 | return {
34 | ...state,
35 | stories: [...state.stories, ...payload.stories],
36 | page: state.page + 1,
37 | isFetching: false,
38 | };
39 | default:
40 | return state;
41 | }
42 | };
43 |
44 | export default story;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
8 |
9 | const mapTypeToRequest = type => ({
10 | request: buildActionCreator(`${type}_REQUEST`),
11 | success: buildActionCreator(`${type}_SUCCESS`),
12 | failure: buildActionCreator(`${type}_FAILURE`),
13 | });
14 |
15 | export const buildRequestCreator = (type, requestCallback) => {
16 | const request = mapTypeToRequest(type);
17 | return (payload = {}) => dispatch => requestCallback({ request, payload, dispatch });
18 | };
--------------------------------------------------------------------------------
/Code/24th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/24th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/24th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const LINK_REL = "noopener noreferrer nofollow";
2 |
3 | export const onAuthRequired = ({ history }) => {
4 | history.push('/login');
5 | }
--------------------------------------------------------------------------------
/Code/25th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/25th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/25th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-redux": "^6.0.1",
12 | "react-router-dom": "^4.3.1",
13 | "react-scripts": "^2.1.8",
14 | "react-timeago": "^4.4.0",
15 | "redux": "^4.0.1",
16 | "redux-logger": "^3.0.6",
17 | "redux-thunk": "^2.3.0",
18 | "styled-components": "^4.1.3",
19 | "url": "^0.11.0"
20 | },
21 | "scripts": {
22 | "start": "react-scripts start",
23 | "build": "react-scripts build",
24 | "test": "react-scripts test",
25 | "eject": "react-scripts eject"
26 | },
27 | "eslintConfig": {
28 | "extends": "react-app"
29 | },
30 | "browserslist": [
31 | ">0.2%",
32 | "not dead",
33 | "not ie <= 11",
34 | "not op_mini all"
35 | ],
36 | "devDependencies": {
37 | "dotenv": "^6.2.0"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Code/25th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/25th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/25th_challenge/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/25th_challenge/src/assets/logo.png
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: auto;
8 | min-height: 100vh;
9 | overflow: hidden;
10 | position: relative;
11 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = ({ stories }) => (
7 |
8 | {
9 | stories.map(story => )
10 | }
11 |
12 | );
13 |
14 | export default List;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TimeAgo from 'react-timeago';
3 |
4 | import { LINK_REL, HN_USER, HN_ITEM, getSiteHostname, getArticleLink } from 'utils';
5 |
6 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
7 |
8 | const ListItem = ({ id, title, url, score, by, time, kids = [] }) => {
9 |
10 | const site = getSiteHostname(url);
11 | const link = getArticleLink({ url, id });
12 | const commentUrl = `${HN_ITEM}${id}`;
13 | const userUrl = `${HN_USER}${by}`;
14 |
15 |
16 | return (
17 | -
18 |
19 |
20 | {title} ({site})
21 |
22 |
23 |
24 | {score} points by
25 | {' '}
26 |
27 | {by}
28 | {' '}
29 |
30 | {' | '}
31 |
32 | {kids.length} Comments
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default ListItem;
40 |
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Login/SignInWidget.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OktaSignIn from '@okta/okta-signin-widget';
4 | import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
5 | import '@okta/okta-signin-widget/dist/css/okta-theme.css';
6 |
7 | import { Wrapper } from './styles';
8 |
9 | import logo from 'assets/logo.png';
10 |
11 | // This is a simple widget created by octa
12 | // We can customize it using our own css
13 | class SignInWidget extends Component {
14 |
15 | componentDidMount() {
16 | const el = ReactDOM.findDOMNode(this);
17 | this.widget = new OktaSignIn({
18 | baseUrl: this.props.baseUrl,
19 | logo: logo,
20 | });
21 | this.widget.renderEl({ el }, this.props.onSuccess, this.props.onError);
22 | }
23 |
24 | componentWillUnmount() {
25 | this.widget.remove();
26 | }
27 |
28 | render() {
29 | return
30 | }
31 | }
32 |
33 | export default SignInWidget;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Login);
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Login/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | display: flex;
5 | height: 100vh;
6 | align-items: center;
7 | justify-content: center;
8 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Nav);
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | z-index: 9999;
13 | `;
14 |
15 | export const Content = styled.div`
16 | height: 100%;
17 | width: 100%;
18 | max-width: 85%;
19 | margin-left: auto;
20 | margin-right: auto;
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | `;
25 |
26 | export const NavSection = styled.ul`
27 | display: flex;
28 | align-items: center;
29 | `;
30 |
31 | export const NavItem = styled.li`
32 | line-height: ${HEIGHT}px;
33 | padding: 0 10px;
34 |
35 | & > button {
36 | cursor: pointer;
37 | background-color: transparent;
38 | border: none;
39 | color: inherit;
40 | font-size: 16px;
41 | }
42 | `;
43 |
44 | export const Spacer = styled.div`
45 | height: ${HEIGHT}px;
46 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/News/News.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import List from 'components/List';
4 |
5 | import { Title, Spinner, NoStories } from './styles';
6 |
7 | class News extends Component {
8 |
9 | componentDidMount() {
10 | this.props.fetchFirstPageStories();
11 | }
12 |
13 |
14 | render() {
15 |
16 | const { stories, isFetching } = this.props;
17 |
18 | return (
19 |
20 |
21 | Hacker News Clone
22 |
23 |
24 | {isFetching ?
25 |
26 | :
27 | (stories.length ?
28 |
29 | :
30 | There is no stories
31 | )
32 | }
33 |
34 |
35 | );
36 | }
37 | }
38 |
39 | export default News;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import actions from 'store/story/actions';
3 |
4 | import News from './News';
5 |
6 | const mapStateToProps = ({ story }) => ({
7 | stories: story.stories,
8 | storyIds: story.storyIds,
9 | page: story.page,
10 | isFetching: story.isFetching,
11 | error: story.error,
12 | });
13 |
14 | const mapDispatchToProps = dispatch => ({
15 | fetchFirstPageStories: () => dispatch(actions.fetchStoryIds()),
16 | fetchStories: ({ storyIds, page }) => dispatch(actions.fetchStories({ storyIds, page })),
17 | });
18 |
19 | export default connect(
20 | mapStateToProps,
21 | mapDispatchToProps
22 | )(News);
--------------------------------------------------------------------------------
/Code/25th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
8 |
9 | export const NoStories = styled.h3`
10 | text-align: center;
11 | font-weight: 300;
12 | text-decoration: line-through;
13 | `;
14 |
15 | const spin = keyframes`
16 | from {
17 | transform: rotate(0deg)
18 | }
19 | to {
20 | transform: rotate(360deg)
21 | }
22 | `;
23 |
24 | export const Spinner = styled.div`
25 | margin: auto;
26 | width: 2rem;
27 | height: 2rem;
28 | border: 4px double;
29 | border-color: ${({ theme }) => theme.text} transparent;
30 | border-radius: 50%;
31 | animation: ${spin} 1s infinite linear;
32 | `;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App';
10 |
11 | const renderApp = () => {
12 |
13 | const initialState = {};
14 | const store = configureStore(initialState);
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 |
21 | , document.getElementById('hn-root'));
22 | }
23 |
24 | renderApp();
25 | registerServiceWorker();
26 |
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 | import story from './story/reducer';
5 |
6 | // The name of each reducer will be the key
7 | const rootReducer = combineReducers({
8 | app,
9 | story,
10 | });
11 |
12 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/story/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions';
2 |
3 | const getInitialState = () => ({
4 | storyIds: [],
5 | stories: [],
6 | page: 0,
7 | isFetching: false,
8 | error: '',
9 | });
10 |
11 | const story = (state = getInitialState(), { type, payload }) => {
12 | switch (type) {
13 | case `${actionTypes.FETCH_STORY_IDS}_REQUEST`:
14 | case `${actionTypes.FETCH_STORIES}_REQUEST`:
15 | return {
16 | ...state,
17 | isFetching: true,
18 | };
19 | case `${actionTypes.FETCH_STORY_IDS}_FAILURE`:
20 | case `${actionTypes.FETCH_STORIES}_FAILURE`:
21 | return {
22 | ...state,
23 | ...payload,
24 | isFetching: false,
25 | };
26 | case `${actionTypes.FETCH_STORY_IDS}_SUCCESS`:
27 | return {
28 | ...state,
29 | ...payload,
30 | isFetching: false,
31 | };
32 | case `${actionTypes.FETCH_STORIES}_SUCCESS`:
33 | return {
34 | ...state,
35 | stories: [...state.stories, ...payload.stories],
36 | page: state.page + 1,
37 | isFetching: false,
38 | };
39 | default:
40 | return state;
41 | }
42 | };
43 |
44 | export default story;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
8 |
9 | const mapTypeToRequest = type => ({
10 | request: buildActionCreator(`${type}_REQUEST`),
11 | success: buildActionCreator(`${type}_SUCCESS`),
12 | failure: buildActionCreator(`${type}_FAILURE`),
13 | });
14 |
15 | export const buildRequestCreator = (type, requestCallback) => {
16 | const request = mapTypeToRequest(type);
17 | return (payload = {}) => dispatch => requestCallback({ request, payload, dispatch });
18 | };
--------------------------------------------------------------------------------
/Code/25th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/25th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/25th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import url from 'url';
2 |
3 | export const LINK_REL = "noopener noreferrer nofollow";
4 |
5 | export const onAuthRequired = ({ history }) => {
6 | history.push('/login');
7 | }
8 |
9 | const HN_ROOT = 'https://news.ycombinator.com';
10 |
11 | export const HN_ITEM = `${HN_ROOT}/item?id=`;
12 | export const HN_USER = `${HN_ROOT}/user?id=`;
13 |
14 | // if we don't have a url this function helps us
15 | // build an internal url using the id of the story
16 | export const getArticleLink = ({ url, id }) => {
17 | const commentUrl = `${HN_ITEM}${id}`;
18 | const link = !!url ? url : commentUrl;
19 | return link;
20 | };
21 |
22 | // We'll use it to extract the hostname from a given url.
23 | // if the given url is undefined, it'll return the hostname of the hn_root
24 | export const getSiteHostname = siteUrl => {
25 |
26 | if(!siteUrl) {
27 | siteUrl = HN_ROOT;
28 | }
29 |
30 | let hostname = '';
31 |
32 | if (siteUrl) {
33 | if (!siteUrl.includes('//')) {
34 | siteUrl = `http://${siteUrl}`;
35 | }
36 |
37 | hostname = url.parse(siteUrl).hostname;
38 | }
39 |
40 | if (hostname.includes('www.')) {
41 | hostname = hostname.split('www.')[1];
42 | }
43 |
44 | return hostname;
45 | };
--------------------------------------------------------------------------------
/Code/26th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 | NODE_PATH=src
--------------------------------------------------------------------------------
/Code/26th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/26th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hacker_news_clone",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@okta/okta-react": "^1.2.0",
7 | "@okta/okta-signin-widget": "^2.17.0",
8 | "axios": "^0.18.0",
9 | "react": "^16.8.3",
10 | "react-dom": "^16.8.3",
11 | "react-infinite-scroller": "^1.2.4",
12 | "react-redux": "^6.0.1",
13 | "react-router-dom": "^4.3.1",
14 | "react-scripts": "^2.1.8",
15 | "react-timeago": "^4.4.0",
16 | "redux": "^4.0.1",
17 | "redux-logger": "^3.0.6",
18 | "redux-thunk": "^2.3.0",
19 | "reselect": "^4.0.0",
20 | "styled-components": "^4.1.3",
21 | "url": "^0.11.0"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": "react-app"
31 | },
32 | "browserslist": [
33 | ">0.2%",
34 | "not dead",
35 | "not ie <= 11",
36 | "not op_mini all"
37 | ],
38 | "devDependencies": {
39 | "dotenv": "^6.2.0"
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Code/26th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 | Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/Code/26th_challenge/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "HNC",
3 | "name": "Hacker News Clone",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "theme_color": "#000000",
7 | "background_color": "#ffffff"
8 | }
--------------------------------------------------------------------------------
/Code/26th_challenge/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/Code/26th_challenge/src/assets/logo.png
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/App/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | width: 85%;
5 | margin-left: auto;
6 | margin-right: auto;
7 | height: auto;
8 | min-height: 100vh;
9 | overflow: hidden;
10 | position: relative;
11 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Home/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import { LINK_REL } from 'utils';
4 |
5 | import { Title, SocialLinks, SocialLink, Wrapper } from './styles';
6 |
7 | const Home = () => (
8 |
9 |
10 | Welcome to Hacker News Clone
11 |
12 |
13 |
14 |
15 |
16 | Ilyasse Benrkia
17 |
18 |
19 |
20 |
21 |
22 | Facebook Developer Circle
23 |
24 |
25 |
26 |
27 |
28 | Facebook Developer Circle
29 |
30 |
31 |
32 |
33 | );
34 |
35 | export default Home;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Home/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | position: absolute;
5 | top: 0;
6 | height: 100%;
7 | width: 100%;
8 | display: flex;
9 | flex-direction: column;
10 | justify-content: center;
11 | align-items: center;
12 | `;
13 |
14 | export const Title = styled.h1`
15 | text-align: center;
16 | font-weight: 300;
17 | & > span {
18 | display: block;
19 | font-weight: bold;
20 | color: white;
21 | }
22 | `;
23 |
24 | export const SocialLinks = styled.ul`
25 | display: flex;
26 | `;
27 |
28 | export const SocialLink = styled.li`
29 | display: flex;
30 | flex-direction: column;
31 | align-items: center;
32 | padding: 0 10px;
33 | flex: 1;
34 |
35 | &:hover {
36 | color: white;
37 | }
38 |
39 | & > i {
40 | font-size: 1.5em;
41 | }
42 |
43 | & > a {
44 | text-align: center;
45 | }
46 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/List/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ListItem from 'components/ListItem';
3 |
4 | import { ListWrapper } from './styles';
5 |
6 | const List = ({ stories }) => (
7 |
8 | {
9 | stories.map(story => )
10 | }
11 |
12 | );
13 |
14 | export default List;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/List/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const ListWrapper = styled.ul`
4 | background-color: ${({ theme }) => theme.backgroundSecondary};
5 | border-radius: 4px;
6 | margin-left: auto;
7 | margin-right: auto;
8 | margin-bottom: 20px;
9 | display: flex;
10 | flex-direction: column;
11 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/ListItem/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TimeAgo from 'react-timeago';
3 |
4 | import { LINK_REL, HN_USER, HN_ITEM, getSiteHostname, getArticleLink } from 'utils';
5 |
6 | import { Item, Title, Host, ExernalLink, Description, CommentLink } from './styles';
7 |
8 | const ListItem = ({ id, title, url, score, by, time, kids = [] }) => {
9 |
10 | const site = getSiteHostname(url);
11 | const link = getArticleLink({ url, id });
12 | const commentUrl = `${HN_ITEM}${id}`;
13 | const userUrl = `${HN_USER}${by}`;
14 |
15 |
16 | return (
17 | -
18 |
19 |
20 | {title} ({site})
21 |
22 |
23 |
24 | {score} points by
25 | {' '}
26 |
27 | {by}
28 | {' '}
29 |
30 | {' | '}
31 |
32 | {kids.length} Comments
33 |
34 |
35 |
36 | );
37 | }
38 |
39 | export default ListItem;
40 |
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/ListItem/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Item = styled.li`
4 | border-bottom: 1px solid ${({ theme }) => theme.border};
5 | padding: 14px 24px;
6 |
7 | &:last-child {
8 | border-bottom: none;
9 | }
10 | `;
11 |
12 | export const Title = styled.h3`
13 | color: ${({ theme }) => theme.text};
14 | margin-top: 0;
15 | margin-bottom: 6px;
16 | font-weight: 400;
17 | font-size: 16px;
18 | letter-spacing: 0.4px;
19 | `;
20 |
21 | export const Host = styled.span`
22 | color: ${({ theme }) => theme.textSecondary};
23 | font-size: 12px;
24 | `;
25 |
26 | export const ExernalLink = styled.a`
27 | color: ${({ theme }) => theme.text};
28 | display: flex;
29 | width: 100%;
30 | height: 100%;
31 | flex-direction: row;
32 | align-items: center;
33 | text-decoration: none;
34 | `;
35 |
36 | export const Description = styled.div`
37 | font-size: 14px;
38 | color: ${({ theme }) => theme.textSecondary};
39 | `;
40 |
41 | export const CommentLink = styled.a`
42 | color: ${({ theme }) => theme.textSecondary};
43 |
44 | &:visited {
45 | color: ${({ theme }) => theme.textSecondary};
46 | }
47 | &:hover {
48 | text-decoration: underline;
49 | }
50 | `;
51 |
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Login/SignInWidget.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import ReactDOM from 'react-dom';
3 | import OktaSignIn from '@okta/okta-signin-widget';
4 | import '@okta/okta-signin-widget/dist/css/okta-sign-in.min.css';
5 | import '@okta/okta-signin-widget/dist/css/okta-theme.css';
6 |
7 | import { Wrapper } from './styles';
8 |
9 | import logo from 'assets/logo.png';
10 |
11 | // This is a simple widget created by octa
12 | // We can customize it using our own css
13 | class SignInWidget extends Component {
14 |
15 | componentDidMount() {
16 | const el = ReactDOM.findDOMNode(this);
17 | this.widget = new OktaSignIn({
18 | baseUrl: this.props.baseUrl,
19 | logo: logo,
20 | });
21 | this.widget.renderEl({ el }, this.props.onSuccess, this.props.onError);
22 | }
23 |
24 | componentWillUnmount() {
25 | this.widget.remove();
26 | }
27 |
28 | render() {
29 | return
30 | }
31 | }
32 |
33 | export default SignInWidget;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Login/index.js:
--------------------------------------------------------------------------------
1 | import Login from './Login';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Login);
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Login/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | export const Wrapper = styled.div`
4 | display: flex;
5 | height: 100vh;
6 | align-items: center;
7 | justify-content: center;
8 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Nav/index.js:
--------------------------------------------------------------------------------
1 | import Nav from './Nav';
2 | import { withAuth } from '@okta/okta-react';
3 |
4 | export default withAuth(Nav);
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/Nav/styles.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const HEIGHT = 48;
4 |
5 | export const Header = styled.header`
6 | background-color: ${({ theme }) => theme.backgroundSecondary};
7 | height: ${HEIGHT}px;
8 | width: 100%;
9 | box-shadow: 0 1px 0 0 black;
10 | position: fixed;
11 | top: 0;
12 | z-index: 9999;
13 | `;
14 |
15 | export const Content = styled.div`
16 | height: 100%;
17 | width: 100%;
18 | max-width: 85%;
19 | margin-left: auto;
20 | margin-right: auto;
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | `;
25 |
26 | export const NavSection = styled.ul`
27 | display: flex;
28 | align-items: center;
29 | `;
30 |
31 | export const NavItem = styled.li`
32 | line-height: ${HEIGHT}px;
33 | padding: 0 10px;
34 |
35 | & > button {
36 | cursor: pointer;
37 | background-color: transparent;
38 | border: none;
39 | color: inherit;
40 | font-size: 16px;
41 | }
42 | `;
43 |
44 | export const Spacer = styled.div`
45 | height: ${HEIGHT}px;
46 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/News/index.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import actions from 'store/story/actions';
3 | import { hasMoreStoriesSelector } from 'store/story/selectors'
4 |
5 | import News from './News';
6 |
7 | const mapStateToProps = ({ story }) => ({
8 | stories: story.stories,
9 | storyIds: story.storyIds,
10 | page: story.page,
11 | isFetching: story.isFetching,
12 | error: story.error,
13 | hasMoreStories: hasMoreStoriesSelector(story)
14 | });
15 |
16 | const mapDispatchToProps = dispatch => ({
17 | fetchFirstPageStories: () => dispatch(actions.fetchStoryIds()),
18 | fetchStories: ({ storyIds, page }) => dispatch(actions.fetchStories({ storyIds, page })),
19 | });
20 |
21 | export default connect(
22 | mapStateToProps,
23 | mapDispatchToProps
24 | )(News);
--------------------------------------------------------------------------------
/Code/26th_challenge/src/components/News/styles.js:
--------------------------------------------------------------------------------
1 | import styled, { keyframes } from 'styled-components';
2 |
3 | export const Title = styled.h1`
4 | color: ${({ theme }) => theme.textSecondary};
5 | font-size: 20px;
6 | font-weight: 300;
7 | `;
8 |
9 | export const NoStories = styled.h3`
10 | text-align: center;
11 | font-weight: 300;
12 | text-decoration: line-through;
13 | `;
14 |
15 | const spin = keyframes`
16 | from {
17 | transform: rotate(0deg)
18 | }
19 | to {
20 | transform: rotate(360deg)
21 | }
22 | `;
23 |
24 | export const Spinner = styled.div`
25 | margin: auto;
26 | width: 2rem;
27 | height: 2rem;
28 | border: 4px double;
29 | border-color: ${({ theme }) => theme.text} transparent;
30 | border-radius: 50%;
31 | animation: ${spin} 1s infinite linear;
32 | `;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import registerServiceWorker from './registerServiceWorker';
4 |
5 | import { Provider } from 'react-redux';
6 | import configureStore from 'store';
7 |
8 | import GlobalStyle from 'styles/global';
9 | import App from 'components/App';
10 |
11 | const renderApp = () => {
12 |
13 | const initialState = {};
14 | const store = configureStore(initialState);
15 |
16 | ReactDOM.render(
17 |
18 |
19 |
20 |
21 | , document.getElementById('hn-root'));
22 | }
23 |
24 | renderApp();
25 | registerServiceWorker();
26 |
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/app/actions.js:
--------------------------------------------------------------------------------
1 | import { buildActionCreator } from 'store/utils'
2 |
3 | // this will be the namespace of this feature
4 | // this will help us groupe functionalities by feature
5 | // instead of having a single file for all the features
6 | const NS = '@hnClone/app';
7 |
8 | export const actionTypes = {
9 | SET_THEME: `${NS}/SET_THEME`,
10 | };
11 |
12 | const actions = {
13 | setTheme: buildActionCreator(actionTypes.SET_THEME),
14 | };
15 |
16 | export default actions;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/app/reducer.js:
--------------------------------------------------------------------------------
1 | import { actionTypes } from './actions'
2 |
3 | const initialState = {
4 | theme: 'dark',
5 | };
6 |
7 | // the app reducer
8 | const app = (state = initialState, { type, payload }) => {
9 |
10 | switch(type) {
11 | case actionTypes.SET_THEME:
12 | return {
13 | ...state,
14 | ...payload,
15 | };
16 | default:
17 | return state;
18 | }
19 |
20 | }
21 |
22 | export default app;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/index.js:
--------------------------------------------------------------------------------
1 | import { createStore } from 'redux';
2 |
3 | import reducer from './reducer';
4 | import middleware from './middleware';
5 |
6 | // we can have initialState from many places (api call...)
7 | // so it's good to have it as an argument of the configure store function
8 | const configureStore = initialState => {
9 | const store = createStore(reducer, initialState, middleware);
10 |
11 | return store;
12 | }
13 |
14 | export default configureStore;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/middleware.js:
--------------------------------------------------------------------------------
1 | import { applyMiddleware, compose } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import { createLogger } from 'redux-logger';
4 |
5 | const isProd = process.env.NODE_ENV === 'production';
6 |
7 | const middlewares = [];
8 | middlewares.push(thunk);
9 |
10 | // If it is not production we'll push our createLogger middleware
11 | // which is a log utility for the chrome console
12 | if(!isProd) {
13 | middlewares.push(createLogger());
14 | }
15 |
16 | // we can get rid of compose for now
17 | // but it's important to compose all the app's middleware into a single one
18 | const middleware = compose(applyMiddleware(...middlewares), window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
19 |
20 | export default middleware;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import app from './app/reducer';
4 | import story from './story/reducer';
5 |
6 | // The name of each reducer will be the key
7 | const rootReducer = combineReducers({
8 | app,
9 | story,
10 | });
11 |
12 | export default rootReducer;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/story/selectors.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect';
2 |
3 | const storiesSelector = story => story.stories;
4 | const storyIdsSelector = story => story.storyIds;
5 |
6 | export const hasMoreStoriesSelector = createSelector(
7 | [storiesSelector, storyIdsSelector],
8 | (stories, storyIds) => stories.length < storyIds.length
9 | );
--------------------------------------------------------------------------------
/Code/26th_challenge/src/store/utils/index.js:
--------------------------------------------------------------------------------
1 | // this utility help us build an action creator
2 | export const buildActionCreator = type => {
3 | return (payload = {}) => ({
4 | type,
5 | payload,
6 | });
7 | }
8 |
9 | const mapTypeToRequest = type => ({
10 | request: buildActionCreator(`${type}_REQUEST`),
11 | success: buildActionCreator(`${type}_SUCCESS`),
12 | failure: buildActionCreator(`${type}_FAILURE`),
13 | });
14 |
15 | export const buildRequestCreator = (type, requestCallback) => {
16 | const request = mapTypeToRequest(type);
17 | return (payload = {}) => dispatch => requestCallback({ request, payload, dispatch });
18 | };
--------------------------------------------------------------------------------
/Code/26th_challenge/src/styles/global.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import { colorsDark } from './palette'
3 |
4 | const GlobalStyle = createGlobalStyle`
5 | * {
6 | box-sizing: border-box;
7 | }
8 | html, body {
9 | font-family: Lato,Helvetica-Neue,Helvetica,Arial,sans-serif;
10 | width: 100vw;
11 | overflow-x: hidden;
12 | margin: 0;
13 | padding: 0;
14 | background-color: ${colorsDark.background};
15 | color: ${colorsDark.text};
16 | }
17 | ul {
18 | list-style: none;
19 | padding: 0;
20 | }
21 | a {
22 | text-decoration: none;
23 | color: inherit;
24 | &:visited {
25 | color: inherit;
26 | }
27 | }
28 | `;
29 |
30 | export default GlobalStyle;
--------------------------------------------------------------------------------
/Code/26th_challenge/src/styles/palette.js:
--------------------------------------------------------------------------------
1 | export const colorsDark = {
2 | background: '#272727',
3 | backgroundSecondary: '#393C3E',
4 | text: '#bfbebe',
5 | textSecondary: '#848886',
6 | border: '#272727',
7 | };
8 |
9 | export const colorsLight = {
10 | background: '#EAEAEA',
11 | backgroundSecondary: '#F8F8F8',
12 | text: '#848886',
13 | textSecondary: '#aaaaaa',
14 | border: '#EAEAEA',
15 | };
--------------------------------------------------------------------------------
/Code/26th_challenge/src/utils/index.js:
--------------------------------------------------------------------------------
1 | import url from 'url';
2 |
3 | export const LINK_REL = "noopener noreferrer nofollow";
4 |
5 | export const onAuthRequired = ({ history }) => {
6 | history.push('/login');
7 | }
8 |
9 | const HN_ROOT = 'https://news.ycombinator.com';
10 |
11 | export const HN_ITEM = `${HN_ROOT}/item?id=`;
12 | export const HN_USER = `${HN_ROOT}/user?id=`;
13 |
14 | // if we don't have a url this function helps us
15 | // build an internal url using the id of the story
16 | export const getArticleLink = ({ url, id }) => {
17 | const commentUrl = `${HN_ITEM}${id}`;
18 | const link = !!url ? url : commentUrl;
19 | return link;
20 | };
21 |
22 | // We'll use it to extract the hostname from a given url.
23 | // if the given url is undefined, it'll return the hostname of the hn_root
24 | export const getSiteHostname = siteUrl => {
25 |
26 | if(!siteUrl) {
27 | siteUrl = HN_ROOT;
28 | }
29 |
30 | let hostname = '';
31 |
32 | if (siteUrl) {
33 | if (!siteUrl.includes('//')) {
34 | siteUrl = `http://${siteUrl}`;
35 | }
36 |
37 | hostname = url.parse(siteUrl).hostname;
38 | }
39 |
40 | if (hostname.includes('www.')) {
41 | hostname = hostname.split('www.')[1];
42 | }
43 |
44 | return hostname;
45 | };
--------------------------------------------------------------------------------
/Code/27th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Render Props
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Code/28th_challenge/.env:
--------------------------------------------------------------------------------
1 | SKIP_PREFLIGHT_CHECK=true
2 |
--------------------------------------------------------------------------------
/Code/28th_challenge/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/Code/28th_challenge/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "28th_challenge",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.8.6",
7 | "react-dom": "^16.8.6",
8 | "react-scripts": "2.1.8"
9 | },
10 | "scripts": {
11 | "start": "react-scripts start",
12 | "build": "react-scripts build",
13 | "test": "react-scripts test",
14 | "eject": "react-scripts eject"
15 | },
16 | "eslintConfig": {
17 | "extends": "react-app"
18 | },
19 | "browserslist": [
20 | ">0.2%",
21 | "not dead",
22 | "not ie <= 11",
23 | "not op_mini all"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/Code/28th_challenge/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 | React Hooks
10 |
11 |
12 | You need to enable JavaScript to run this app.
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/Code/28th_challenge/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | import Users from './Users';
4 | import useStorage from './useStorage';
5 |
6 | const App = () => (
7 |
10 | );
11 |
12 | export default App;
--------------------------------------------------------------------------------
/Code/28th_challenge/src/User.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const User = ({ user }) => {
4 |
5 | const userDivStyle = {
6 | backgroundColor: '#607D8B',
7 | padding: '5px 10px',
8 | }
9 |
10 | const margin5 = {
11 | margin: '5px 0',
12 | }
13 |
14 | return (
15 |
16 |
{user.name}
17 |
{user.email}
18 |
19 | );
20 | }
21 |
22 | export default User;
--------------------------------------------------------------------------------
/Code/28th_challenge/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | ReactDOM.render( , document.getElementById('root'));
6 |
--------------------------------------------------------------------------------
/Code/28th_challenge/src/useStorage.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | const useStorage = () => {
4 |
5 | const [localStorageAvailable, setLocalStorageAvailable] = useState(true);
6 |
7 | useEffect(() => {
8 |
9 | const testItem = 'random';
10 |
11 | try {
12 | localStorage.setItem(testItem, testItem);
13 | localStorage.removeItem(testItem);
14 | setLocalStorageAvailable(true);
15 | } catch(e) {
16 | setLocalStorageAvailable(false);
17 | }
18 |
19 | });
20 |
21 | const isLocalStorageAvailable = () => {
22 | return localStorageAvailable;
23 | }
24 |
25 | const load = (key) => {
26 |
27 | if(isLocalStorageAvailable()) {
28 | return JSON.parse(localStorage.getItem(key));
29 | }
30 |
31 | return null;
32 | }
33 |
34 | const save = (key, data) => {
35 |
36 | if(isLocalStorageAvailable()) {
37 | localStorage.setItem(key, JSON.stringify(data));
38 | }
39 | }
40 |
41 | const remove = (key) => {
42 |
43 | if(isLocalStorageAvailable()) {
44 | localStorage.removeItem(key);
45 | }
46 | }
47 |
48 | return { load, save, remove };
49 | }
50 |
51 | export default useStorage;
--------------------------------------------------------------------------------
/Code/2nd_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Elements rendering
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Code/2nd_challenge/solution.js:
--------------------------------------------------------------------------------
1 | const colors = ['red', 'green', 'black', 'blue', 'brown'];
2 | const colorsLength = colors.length;
3 | let i = 0;
4 |
5 | function tick(colors) {
6 | let class_name="circle "+colors[i%colorsLength];
7 | const element = (
);
8 | ReactDOM.render(element, document.getElementById('root'));
9 | i++;
10 | }
11 |
12 | setInterval(tick, 1000, colors);
--------------------------------------------------------------------------------
/Code/3rd_challenge/challenge.js:
--------------------------------------------------------------------------------
1 | /**
2 | * modify the bellow code in order to make it work
3 | * use stateless/function components
4 | */
5 |
6 | const items = [
7 | {title: 'item 1', url: 'https://google.com/'},
8 | {title: 'item 2', url: 'https://google.com/'},
9 | {title: 'item 3', url: 'https://google.com/'},
10 | {title: 'item 4', url: 'https://google.com/'},
11 | {title: 'item 5', url: 'https://google.com/'},
12 | ];
13 |
14 | const itemsElements = items.map(item =>
15 |
16 | {item.title} visit
17 |
18 | );
19 |
20 | const headerProps = {
21 | className: 'hidden',
22 | children: [
23 | Title goes here ,
24 | Description goes here
25 | ],
26 | }
27 |
28 | const element = (
29 |
30 |
31 | {itemsElements}
32 |
33 | );
34 |
35 | ReactDOM.render(element, document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/3rd_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Stateless Components
8 |
9 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/Code/3rd_challenge/solution.js:
--------------------------------------------------------------------------------
1 | const items = [
2 | {title: 'item 1', url: 'https://google.com/'},
3 | {title: 'item 2', url: 'https://google.com/'},
4 | {title: 'item 3', url: 'https://google.com/'},
5 | {title: 'item 4', url: 'https://google.com/'},
6 | {title: 'item 5', url: 'https://google.com/'},
7 | ];
8 |
9 | const Item = (props) => {
10 | const {item} = props;
11 | return (
12 |
13 | {item.title} visit
14 |
15 | );
16 | }
17 |
18 | const headerProps = {
19 | className: 'hidden',
20 | children: [
21 | Title goes here ,
22 | Description goes here
23 | ],
24 | }
25 |
26 | const element = (
27 |
28 |
29 |
30 | {
31 | items.map(item => )
32 | }
33 |
34 |
35 | );
36 |
37 | ReactDOM.render(element, document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/4th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Components & Props
8 |
9 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/Code/5th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React Stateful Components
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Code/5th_challenge/solution.js:
--------------------------------------------------------------------------------
1 | const colors = ['red', 'green', 'black', 'blue', 'brown'];
2 |
3 | class Circle extends React.Component {
4 |
5 | constructor(props) {
6 | super(props);
7 |
8 | const {colors} = props;
9 | this.state = {
10 | colors,
11 | colorsLength: colors.length,
12 | i: 0
13 | };
14 | }
15 |
16 | componentDidMount() {
17 | this.timerID = setInterval(
18 | () => this.tick(),
19 | 1000
20 | );
21 | }
22 |
23 | componentWillUnmount() {
24 | clearInterval(this.timerID);
25 | }
26 |
27 | tick() {
28 | const {colorsLength, i} = this.state;
29 | this.setState({
30 | i: (i+1)%colorsLength
31 | });
32 | }
33 |
34 | render() {
35 |
36 | const {colors, i} = this.state;
37 | const class_name = 'circle '+colors[i];
38 |
39 | return (
40 |
41 | );
42 |
43 | }
44 |
45 | }
46 |
47 | ReactDOM.render( , document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/6th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | React LifeCycle Hooks
8 |
9 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/Code/7th_challenge/challenge.js:
--------------------------------------------------------------------------------
1 | // what is the problem with this code.
2 | // try to change the core of onIncrement function in order to make it work.
3 |
4 | const Counter = ({count, onIncrement}) => {
5 |
6 | const incrementOnce = () => {
7 | onIncrement();
8 | }
9 |
10 | const incrementTwice = () => {
11 | onIncrement();
12 | onIncrement();
13 | }
14 |
15 | return (
16 |
17 |
{count}
18 | Increment Once
19 | Increment Twice
20 |
21 | );
22 | }
23 |
24 | class App extends React.Component {
25 |
26 | state = {
27 | count: 0
28 | };
29 |
30 | onIncrement = () => {
31 | this.setState({count: this.state.count+1});
32 | }
33 |
34 | render() {
35 |
36 | const {count} = this.state;
37 |
38 | return (
39 |
40 |
41 |
42 | );
43 | }
44 | }
45 |
46 | ReactDOM.render( , document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/7th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Asynchronous State
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Code/7th_challenge/solution.js:
--------------------------------------------------------------------------------
1 | const Counter = ({count, onIncrement}) => {
2 |
3 | const incrementOnce = () => {
4 | onIncrement();
5 | }
6 |
7 | const incrementTwice = () => {
8 | onIncrement();
9 | onIncrement();
10 | }
11 |
12 | return (
13 |
14 |
{count}
15 | Increment Once
16 | Increment Twice
17 |
18 | );
19 | }
20 |
21 | class App extends React.Component {
22 |
23 | state = {
24 | count: 0
25 | };
26 |
27 | onIncrement = () => {
28 | this.setState((state) => ({
29 | count: state.count+1,
30 | }));
31 | }
32 |
33 | render() {
34 |
35 | const {count} = this.state;
36 |
37 | return (
38 |
39 |
40 |
41 | );
42 | }
43 | }
44 |
45 | ReactDOM.render( , document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/8th_challenge/challenge.js:
--------------------------------------------------------------------------------
1 | // The objective of this challenge is to both:
2 | // 1_ test your understanding for what we've done
3 | // 2_ know how the handle event in react
4 |
5 | // Fix this code such as the user should be able to tape inside the input and with each change to component should show the result.
6 |
7 | class Greet extends React.Component {
8 |
9 | constructor() {
10 | state = {
11 | name: ''
12 | }
13 | }
14 |
15 | function greeting(event) {
16 | const name = event.target.value;
17 | this.state.name = name;
18 | }
19 |
20 |
21 | function render() {
22 |
23 | const name = this.state;
24 |
25 | return (
26 |
30 | {name}
31 | );
32 | }
33 |
34 | }
35 |
36 | class App extends React.Component {
37 |
38 | function render() {
39 | return(
40 |
41 | );
42 | }
43 |
44 | }
45 |
46 |
47 | ReactDOM.render( , document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/8th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Events Handling
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/Code/8th_challenge/solution.js:
--------------------------------------------------------------------------------
1 | class Greet extends React.Component {
2 |
3 | constructor(props) {
4 | super(props);
5 |
6 | this.state = {
7 | name: ''
8 | }
9 | }
10 |
11 | greeting = (event) => {
12 | const name = event.target.value;
13 | this.setState({name});
14 | }
15 |
16 |
17 | render() {
18 |
19 | const {name} = this.state;
20 |
21 | return (
22 |
23 |
27 |
{name}
28 |
29 | );
30 | }
31 |
32 | }
33 |
34 | class App extends React.Component {
35 |
36 | render() {
37 | return(
38 |
39 | );
40 | }
41 |
42 | }
43 |
44 |
45 | ReactDOM.render( , document.getElementById('root'));
--------------------------------------------------------------------------------
/Code/9th_challenge/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Packaging and Type Checking
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # I've joined the #30DaysOfReact Challenge
2 |
3 | `30 days of react`: is a challenge that aims at learning react by allocating at least 1h every day, to learn and practice react, for the next 30 days.
4 | \
5 | The purpose/utility of this challenge is stop dreaming of sit down and start coding or setting some good intentions on dedicating one hour each night for coding.
6 | \
7 | Rather, by committing to this challenge, and because it's a public commitment as we all are going to follow your updates , you'll be able to stick to this new habit.
8 | \
9 | **So let’s do this together!**
10 |
11 | ---
12 |
13 | ## In this repo
14 |
15 | > [Rules](rules.md)
16 | Rules of the 30 Days Of React Challenge.
17 |
18 | > [Log](log.md)
19 | Here sits the progress of each day.
20 |
21 | > [Code](Code/)
22 | This folder contains all the codes written during this challange.
--------------------------------------------------------------------------------
/img/flux-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/img/flux-diagram.png
--------------------------------------------------------------------------------
/img/thanks.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/geeksblabla/30DaysOfReact/f0803958f820dd04e542258b7b25629dbb144196/img/thanks.jpg
--------------------------------------------------------------------------------