├── .editorconfig
├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── README.md
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── src
├── components
│ ├── button.js
│ ├── content.js
│ ├── form
│ │ ├── error.js
│ │ ├── formGroup.js
│ │ └── input.js
│ ├── header.js
│ ├── spinner.js
│ └── theme.js
├── index.js
├── registerServiceWorker.js
├── routes
│ ├── base-styles.js
│ ├── index.js
│ └── private-route
│ │ └── index.js
├── state
│ ├── auth
│ │ ├── actions.js
│ │ ├── hooks
│ │ │ └── useLogin.js
│ │ ├── queries.js
│ │ └── reducers.js
│ ├── index.js
│ ├── product
│ │ ├── actions.js
│ │ ├── hooks
│ │ │ └── useProducts.js
│ │ ├── queries.js
│ │ └── reducers.js
│ └── reducers.js
├── utils
│ └── services.js
└── views
│ ├── home
│ ├── components
│ │ ├── card.js
│ │ └── list.js
│ └── index.js
│ └── login
│ ├── containers
│ ├── container.js
│ └── form.js
│ └── index.js
└── yarn.lock
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = lf
5 | indent_style = space
6 | indent_size = 2
7 | charset = utf-8
8 | trim_trailing_whitespace = true
9 | insert_final_newline = true
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | },
6 | extends: ['airbnb', 'prettier', 'prettier/react'],
7 | globals: {
8 | Atomics: 'readonly',
9 | SharedArrayBuffer: 'readonly',
10 | },
11 | parser: 'babel-eslint',
12 | parserOptions: {
13 | ecmaFeatures: {
14 | jsx: true,
15 | },
16 | ecmaVersion: 2018,
17 | sourceType: 'module',
18 | },
19 | plugins: ['react', 'prettier'],
20 | rules: {
21 | 'prettier/prettier': 'error',
22 | 'react/jsx-filename-extension': ['warn', { extensions: ['.jsx', '.js'] }],
23 | 'import/prefer-default-export': 'off',
24 | 'react/prop-types': 0,
25 | },
26 | };
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "singleQuote": true,
3 | "trailingComma": "es5"
4 | }
5 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-boilerplate
2 |
3 |
4 | This project is a boilerplate for React, including:
5 |
6 | - React
7 | - Context
8 | - Hooks (useReducer, useContext)
9 | - State Global using Context + Hooks
10 | - Custom Hooks (Like redux Thunks)
11 | - Axios
12 | - Simple Authentication
13 | - Private-Route
14 | - Styled-component with GlobalStyle
15 |
16 | # Build
17 |
18 | Run: ```npm install```
19 |
20 | After run: ```npm start```
21 |
22 |
23 | Enjoy and feels free to create new features and open pull requests!
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-base",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "axios": "^0.21.1",
7 | "formik": "^1.5.4",
8 | "react": "^16.8.6",
9 | "react-dom": "^16.8.6",
10 | "react-router-dom": "^4.3.1",
11 | "react-scripts": "^3.3.0",
12 | "styled-components": "^4.2.0",
13 | "styled-reset": "^2.0.11"
14 | },
15 | "devDependencies": {
16 | "eslint": "^6.1.0",
17 | "eslint-config-airbnb": "^18.0.0",
18 | "eslint-config-prettier": "^6.0.0",
19 | "eslint-plugin-import": "^2.18.2",
20 | "eslint-plugin-jsx-a11y": "^6.2.3",
21 | "eslint-plugin-prettier": "^3.1.0",
22 | "eslint-plugin-react": "^7.14.3",
23 | "eslint-plugin-react-hooks": "^1.7.0",
24 | "prettier": "^1.18.2"
25 | },
26 | "scripts": {
27 | "start": "react-scripts start",
28 | "build": "react-scripts build",
29 | "test": "react-scripts test --env=jsdom",
30 | "eject": "react-scripts eject",
31 | "lint": "eslint . --ext .js,.jsx,.ts,.tsx"
32 | },
33 | "browserslist": {
34 | "production": [
35 | ">0.2%",
36 | "not dead",
37 | "not op_mini all"
38 | ],
39 | "development": [
40 | "last 1 chrome version",
41 | "last 1 firefox version",
42 | "last 1 safari version"
43 | ]
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/viniarruda/react-boilerplate/871e4121b067217012e7b1278356e6882e43d6ec/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
14 |
23 | React App
24 |
25 |
26 |
29 |
30 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/button.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import theme from './theme';
3 |
4 | const Button = styled.button`
5 | transition: all 0.3s ease;
6 | background: ${props =>
7 | theme.colors[Object.keys(props).find(p => theme.colors[p])] ||
8 | theme.colors.primary};
9 | text-transform: ${props => (props.upper ? 'uppercase' : 'none')};
10 | font-weight: 300;
11 | color: ${theme.colors.default};
12 | padding: 9px 13px;
13 | margin: 1px;
14 | border: none;
15 | border-radius: 2px;
16 | cursor: pointer;
17 | width: ${props => (props.large ? '100%' : 'auto')};
18 | &:hover {
19 | opacity: 0.7;
20 | }
21 | `;
22 |
23 | export default Button;
24 |
--------------------------------------------------------------------------------
/src/components/content.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Content = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | width: 100%;
9 | `;
10 |
11 | export default Content;
12 |
--------------------------------------------------------------------------------
/src/components/form/error.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import theme from '../theme';
3 |
4 | const ErrorText = styled.span`
5 | text-align: center;
6 | color: ${theme.forms.errorColor};
7 | `;
8 |
9 | export default ErrorText;
10 |
--------------------------------------------------------------------------------
/src/components/form/formGroup.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const formGroup = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | align-items: center;
7 | justify-content: center;
8 | `;
9 |
10 | export default formGroup;
11 |
--------------------------------------------------------------------------------
/src/components/form/input.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Input = styled.input`
4 | margin: 10px 5px;
5 | border-radius: 3px;
6 | border: 1px solid #e6e6e6;
7 | padding: 7px;
8 | box-sizing: border-box;
9 | width: 100%;
10 | `;
11 |
12 | export default Input;
13 |
--------------------------------------------------------------------------------
/src/components/header.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 |
4 | import { useStateValue } from '../state';
5 | import { logout } from '../state/auth/actions';
6 |
7 | import Button from './button';
8 |
9 | const Nav = styled.div`
10 | height: 50px;
11 | width: 100%;
12 | display: flex;
13 | align-items: center;
14 | padding: 0 20px;
15 | background: #f8f9fa;
16 | position: ${props => (props.fixed ? 'fixed' : 'relative')};
17 | `;
18 |
19 | const Right = styled.nav`
20 | flex: 1;
21 | text-align: right;
22 | `;
23 |
24 | const Title = styled.h1`
25 | margin: 0;
26 | color: #000;
27 | font-weight: 600;
28 | `;
29 |
30 | const Header = props => {
31 | const [{ auth }, dispatch] = useStateValue();
32 |
33 | const handleLogout = async () => {
34 | await dispatch(logout());
35 | };
36 |
37 | return (
38 |
48 | );
49 | };
50 |
51 | export default Header;
52 |
--------------------------------------------------------------------------------
/src/components/spinner.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled from 'styled-components';
3 | import theme from './theme';
4 |
5 | const BackDrop = styled.div`
6 | justify-content: center;
7 | position: absolute;
8 | top: 0;
9 | bottom: 0;
10 | left: 0;
11 | right: 0;
12 | background: rgba(0, 0, 0, 0.67);
13 | `;
14 |
15 | const Spinner = styled.div`
16 | border: 4px solid #f3f3f3;
17 | border-top: 4px solid ${theme.colors.primary};
18 | border-radius: 50%;
19 | width: 120px;
20 | height: 120px;
21 | animation: spin 2s linear infinite;
22 | margin: 40vh auto;
23 | @keyframes spin {
24 | 0% {
25 | transform: rotate(0deg);
26 | }
27 | 100% {
28 | transform: rotate(360deg);
29 | }
30 | }
31 | @-webkit-keyframes spin {
32 | 0% {
33 | -webkit-transform: rotate(0deg);
34 | }
35 | 100% {
36 | -webkit-transform: rotate(360deg);
37 | }
38 | }
39 | `;
40 |
41 | const Loading = props => {
42 | return (
43 | props.show && (
44 |
45 |
46 |
47 | )
48 | );
49 | };
50 |
51 | export default Loading;
52 |
--------------------------------------------------------------------------------
/src/components/theme.js:
--------------------------------------------------------------------------------
1 | const colors = {
2 | primary: '#7159c1',
3 | secondary: '#9b49c1',
4 | default: '#fff',
5 | gradient:
6 | 'linear-gradient(112deg, rgba(113,89,193,1) 0%, rgba(155,73,193,1) 100%);',
7 | bg: 'linear-gradient(to top, #a18cd1 0%, #fbc2eb 100%);',
8 | };
9 |
10 | const viewports = {
11 | smartphone: '360px',
12 | tablet: '720px',
13 | desktop: '1280px',
14 | };
15 |
16 | const forms = {
17 | labelColor: colors.light,
18 | errorColor: colors.danger,
19 | };
20 |
21 | const theme = Object.freeze({
22 | colors,
23 | forms,
24 | viewports,
25 | });
26 |
27 | export default theme;
28 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import Root from './routes';
4 |
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/routes/base-styles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import reset from 'styled-reset';
3 |
4 | const BaseStyles = createGlobalStyle`
5 | ${reset}
6 | body { margin: 0 };
7 | html { font-family: 'Montserrat', sans-serif; }
8 | * { box-sizing: border-box; };
9 | `;
10 |
11 | export default BaseStyles;
12 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
3 | import { StateProvider } from '../state';
4 | import { INITIAL_STATE as AUTH_INITIAL_STATE } from '../state/auth/reducers';
5 | import { INITIAL_STATE as PRODUCT_INITIAL_STATE } from '../state/product/reducers';
6 | import reducers from '../state/reducers';
7 | import BaseStyles from './base-styles';
8 | import PrivateRoute from './private-route';
9 | import Content from '../components/content';
10 | import Header from '../components/header';
11 | import Login from '../views/login';
12 | import Home from '../views/home';
13 |
14 | const Root = props => {
15 | const initialState = {
16 | auth: AUTH_INITIAL_STATE,
17 | product: PRODUCT_INITIAL_STATE,
18 | };
19 | return (
20 |
21 |
22 |
23 | <>
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | >
32 |
33 |
34 | );
35 | };
36 |
37 | export default Root;
38 |
--------------------------------------------------------------------------------
/src/routes/private-route/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Redirect } from 'react-router-dom';
3 |
4 | import { useStateValue } from '../../state';
5 |
6 | const PrivateRoute = ({ component: Component, ...rest }) => {
7 | const [{ auth }] = useStateValue();
8 |
9 | return (
10 |
13 | auth.logged ? (
14 |
15 | ) : (
16 |
17 | )
18 | }
19 | />
20 | );
21 | };
22 |
23 | export default PrivateRoute;
24 |
--------------------------------------------------------------------------------
/src/state/auth/actions.js:
--------------------------------------------------------------------------------
1 | export const LOGIN = 'auth/LOGIN';
2 | export const LOGOUT = 'auth/CLEAR_USER';
3 |
4 | export const login = () => ({
5 | type: LOGIN,
6 | });
7 |
8 | export const logout = () => ({
9 | type: LOGOUT,
10 | });
11 |
--------------------------------------------------------------------------------
/src/state/auth/hooks/useLogin.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useStateValue } from '../../index';
3 | import { login } from '../actions';
4 |
5 | const useLogin = () => {
6 | const [{ auth }, dispatch] = useStateValue();
7 | const [isLoading, setIsLoading] = useState(false);
8 |
9 | const formData = async ({ values, actions }) => {
10 | setIsLoading(true);
11 |
12 | setIsLoading(false);
13 | if (values.username !== '' && values.password !== '') {
14 | dispatch(login());
15 | }
16 | };
17 |
18 | return [auth, formData, isLoading];
19 | };
20 |
21 | export default useLogin;
22 |
--------------------------------------------------------------------------------
/src/state/auth/queries.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 |
3 | export const loadBox = () => {
4 | return axios
5 | .get('https://jsonplaceholder.typicode.com/posts')
6 | .then(res => res.data)
7 | .catch(err => err.response.data);
8 | };
9 |
--------------------------------------------------------------------------------
/src/state/auth/reducers.js:
--------------------------------------------------------------------------------
1 | import { LOGIN, LOGOUT } from './actions';
2 |
3 | export const INITIAL_STATE = {
4 | logged: false,
5 | };
6 |
7 | export default (state = INITIAL_STATE, action) => {
8 | switch (action.type) {
9 | case LOGIN:
10 | return {
11 | logged: true,
12 | };
13 | case LOGOUT:
14 | return {
15 | ...INITIAL_STATE,
16 | };
17 | default:
18 | return state;
19 | }
20 | };
21 |
--------------------------------------------------------------------------------
/src/state/index.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useReducer } from 'react';
2 |
3 | export const StateContext = createContext();
4 |
5 | export const StateProvider = ({ reducer, initialState, children }) => (
6 |
7 | {children}
8 |
9 | );
10 |
11 | export const useStateValue = () => useContext(StateContext);
12 |
--------------------------------------------------------------------------------
/src/state/product/actions.js:
--------------------------------------------------------------------------------
1 | export const LIST_PRODUCTS = 'product/LIST_PRODUCTS';
2 |
3 | export const listProducts = payload => ({
4 | type: LIST_PRODUCTS,
5 | payload,
6 | });
7 |
--------------------------------------------------------------------------------
/src/state/product/hooks/useProducts.js:
--------------------------------------------------------------------------------
1 | import { useState } from 'react';
2 | import { useStateValue } from '../../index';
3 | import { loadProducts } from '../queries';
4 | import { listProducts } from '../actions';
5 |
6 | const useProducts = () => {
7 | const [{ product }, dispatch] = useStateValue();
8 | const [isLoading, setIsLoading] = useState(false);
9 |
10 | const request = async () => {
11 | setIsLoading(true);
12 |
13 | const response = await loadProducts();
14 |
15 | if (response) {
16 | dispatch(listProducts(response));
17 | } else {
18 | const err = [];
19 | dispatch(listProducts(err));
20 | }
21 | setIsLoading(false);
22 | };
23 |
24 | return [product, isLoading, request];
25 | };
26 |
27 | export default useProducts;
28 |
--------------------------------------------------------------------------------
/src/state/product/queries.js:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import api from '../../utils/services';
3 |
4 | export const loadProducts = () => {
5 | return axios
6 | .get(`${api.url}/products`)
7 | .then(res => res.data)
8 | .catch(err => err.response.data);
9 | };
10 |
--------------------------------------------------------------------------------
/src/state/product/reducers.js:
--------------------------------------------------------------------------------
1 | import { LIST_PRODUCTS } from './actions';
2 |
3 | export const INITIAL_STATE = {
4 | list: null,
5 | };
6 |
7 | export default (state = INITIAL_STATE, action) => {
8 | switch (action.type) {
9 | case LIST_PRODUCTS:
10 | return {
11 | ...state,
12 | list: action.payload,
13 | };
14 | default:
15 | return state;
16 | }
17 | };
18 |
--------------------------------------------------------------------------------
/src/state/reducers.js:
--------------------------------------------------------------------------------
1 | import authReducer from './auth/reducers';
2 | import productReducer from './product/reducers';
3 |
4 | export default ({ auth, product }, action) => ({
5 | auth: authReducer(auth, action),
6 | product: productReducer(product, action),
7 | });
8 |
--------------------------------------------------------------------------------
/src/utils/services.js:
--------------------------------------------------------------------------------
1 | const api = {
2 | url: 'https://5eb454842b81f700163084b3.mockapi.io',
3 | token: '',
4 | };
5 |
6 | export default api;
7 |
--------------------------------------------------------------------------------
/src/views/home/components/card.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 | import theme from '../../../components/theme';
3 |
4 | const Card = styled.li`
5 | border-radius: 3px;
6 | background: ${theme.colors.bg};
7 | margin: 10px;
8 | padding: 15px;
9 | `;
10 |
11 | export default Card;
12 |
--------------------------------------------------------------------------------
/src/views/home/components/list.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const List = styled.ul`
4 | display: flex;
5 | flex-flow: row wrap;
6 | `;
7 |
8 | export default List;
9 |
--------------------------------------------------------------------------------
/src/views/home/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 | import useProducts from '../../state/product/hooks/useProducts';
3 |
4 | import Spinner from '../../components/spinner';
5 | import List from './components/list';
6 | import Card from './components/card';
7 | import Button from '../../components/button';
8 |
9 | const Home = () => {
10 | const [product, isLoading, setListProducts] = useProducts();
11 |
12 | useEffect(() => {
13 | if (!product.list || product.list.length === 0) {
14 | setListProducts();
15 | }
16 | }, [product, setListProducts]);
17 |
18 | return (
19 |
20 |
21 |
24 |
25 | {product?.list?.map(p => (
26 |
27 | {p.title}
28 | ${p.price}
29 |
30 | ))}
31 |
32 |
33 | );
34 | };
35 |
36 | export default Home;
37 |
--------------------------------------------------------------------------------
/src/views/login/containers/container.js:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components';
2 |
3 | const Container = styled.div`
4 | display: flex;
5 | flex-direction: column;
6 | justify-content: center;
7 | align-items: center;
8 | height: calc(100vh - 50px);
9 | `;
10 |
11 | export default Container;
12 |
--------------------------------------------------------------------------------
/src/views/login/containers/form.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react/jsx-props-no-spreading */
2 | import React from 'react';
3 | import { Formik } from 'formik';
4 | import Button from '../../../components/button';
5 | import FormGroup from '../../../components/form/formGroup';
6 | import Input from '../../../components/form/input';
7 | import ErrorText from '../../../components/form/error';
8 |
9 | const LoginForm = props => (
10 |
11 | {({ handleChange, handleBlur, values, handleSubmit, errors }) => (
12 |
13 | {errors.genericError && {errors.genericError}}
14 |
23 |
33 |
36 |
37 | )}
38 |
39 | );
40 |
41 | export default LoginForm;
42 |
--------------------------------------------------------------------------------
/src/views/login/index.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from 'react';
2 |
3 | import useLogin from '../../state/auth/hooks/useLogin';
4 |
5 | import Container from './containers/container';
6 | import Form from './containers/form';
7 | import Spinner from '../../components/spinner';
8 |
9 | const Login = ({ location, history }) => {
10 | const { from } = location.state || { from: { pathname: '/home' } };
11 | const [auth, setLogin, isLoading] = useLogin();
12 |
13 | useEffect(() => {
14 | if (auth.logged) {
15 | history.push(from);
16 | }
17 | }, [auth, from, history]);
18 |
19 | return (
20 |
21 |
22 |
24 | );
25 | };
26 |
27 | export default Login;
28 |
--------------------------------------------------------------------------------