├── .env.development ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── components │ ├── AddTask.js │ ├── App.js │ ├── AppWrapper.js │ ├── Image.js │ ├── Login.js │ ├── Register.js │ ├── Task.js │ ├── Tasks.js │ ├── common │ │ ├── Context.js │ │ ├── Form.js │ │ ├── Layout.js │ │ ├── NotFound.js │ │ ├── Seo.js │ │ ├── TasksLoading.js │ │ └── layout.css │ └── theme │ │ └── Header.js ├── helpers │ └── setAuthToken.js ├── images │ ├── gatsby-astronaut.png │ └── gatsby-icon.png ├── pages │ ├── 404.js │ ├── app.js │ └── index.js └── providers │ ├── Provider.js │ ├── taskReducer.js │ └── userReducer.js └── yarn.lock /.env.development: -------------------------------------------------------------------------------- 1 | API=http://localhost:5000/api -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "trailingComma": "es5" 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "javascript.format.enable": false, 4 | "prettier.eslintIntegration": true 5 | } 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Not Todo Gatsby App connected to a REST API 2 | 3 | [Gatsby Talk](https://building-apps-with-gatsby.netlify.com/) 4 | [REST API](https://github.com/smakosh/not-todo-api) 5 | 6 | ## Prerequisites 7 | 8 | [Yarn](https://yarnpkg.com/en/) 9 | 10 | Please create a new file `.env.development` and put this env variable 11 | 12 | > If you're building locally, you will have to create a new file `.env.production` and put the same env variable 13 | 14 | ```bash 15 | API=http://localhost:5000/api 16 | ``` 17 | 18 | ## Installing 19 | 20 | Installing the dependencies 21 | 22 | ```bash 23 | yarn 24 | ``` 25 | 26 | ## Start the REST API 27 | 28 | [Documentation](https://github.com/smakosh/not-todo-api) 29 | 30 | ## Start the dev server 31 | 32 | ```bash 33 | yarn start 34 | ``` 35 | 36 | ### Clean the cache 37 | 38 | This removes the `.cache/` & `public/` folders 39 | 40 | ```bash 41 | yarn reset 42 | ``` 43 | 44 | ## Built with 45 | 46 | - Gatsby 47 | - React & GraphQL 48 | - VSCode 49 | - Unnamed 50 | - And these useful of JavaScript libraries & Gatsby plugins [package.json](package.json) 51 | 52 | ## License 53 | 54 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for details 55 | 56 | ## Support 57 | 58 | If you love this Gatsby template and want to support me, you can do so through my Patreon 59 | 60 | [![Support me on Patreon](https://c5.patreon.com/external/logo/become_a_patron_button.png)](https://www.patreon.com/smakosh) 61 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's Browser APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/browser-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config({ 2 | path: `.env.${process.env.NODE_ENV}`, 3 | }) 4 | 5 | module.exports = { 6 | siteMetadata: { 7 | title: `Not Todo app`, 8 | description: `A demo Gatsby app connected to a REST API`, 9 | author: `@smakosh`, 10 | }, 11 | plugins: [ 12 | `gatsby-plugin-react-helmet`, 13 | { 14 | resolve: `gatsby-source-filesystem`, 15 | options: { 16 | name: `images`, 17 | path: `${__dirname}/src/images`, 18 | }, 19 | }, 20 | `gatsby-transformer-sharp`, 21 | `gatsby-plugin-sharp`, 22 | { 23 | resolve: `gatsby-plugin-manifest`, 24 | options: { 25 | name: `gatsby-starter-default`, 26 | short_name: `starter`, 27 | start_url: `/`, 28 | background_color: `#663399`, 29 | theme_color: `#663399`, 30 | display: `minimal-ui`, 31 | icon: `src/images/gatsby-icon.png`, // This path is relative to the root of the site. 32 | }, 33 | }, 34 | // this (optional) plugin enables Progressive Web App + Offline functionality 35 | // To learn more, visit: https://gatsby.dev/offline 36 | // `gatsby-plugin-offline`, 37 | ], 38 | } 39 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | exports.onCreatePage = async ({ page, actions: { createPage } }) => { 4 | if (page.path.match(/^\/app/)) { 5 | page.matchPath = '/app/*' 6 | createPage(page) 7 | } 8 | } 9 | 10 | exports.onCreateWebpackConfig = ({ actions }) => { 11 | actions.setWebpackConfig({ 12 | resolve: { 13 | modules: [path.resolve(__dirname, 'src'), 'node_modules'], 14 | }, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file. 3 | * 4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/ 5 | */ 6 | 7 | // You can delete this file if you're not using it 8 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "baseUrl": "src", 6 | "experimentalDecorators": true, 7 | "allowSyntheticDefaultImports": true, 8 | "jsx": "react" 9 | }, 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "not-todo-app", 3 | "private": true, 4 | "description": "A demo Gatsby app connected to a REST API", 5 | "version": "0.1.0", 6 | "author": "Ismail Ghallou ", 7 | "dependencies": { 8 | "axios": "^0.18.0", 9 | "dotenv": "^8.0.0", 10 | "gatsby": "^2.4.3", 11 | "gatsby-image": "^2.0.41", 12 | "gatsby-plugin-manifest": "^2.1.1", 13 | "gatsby-plugin-offline": "^2.1.0", 14 | "gatsby-plugin-react-helmet": "^3.0.12", 15 | "gatsby-plugin-sharp": "^2.0.37", 16 | "gatsby-source-filesystem": "^2.0.33", 17 | "gatsby-transformer-sharp": "^2.1.19", 18 | "prop-types": "^15.7.2", 19 | "react": "^16.8.6", 20 | "react-content-loader": "^4.2.1", 21 | "react-dom": "^16.8.6", 22 | "react-helmet": "^5.2.1", 23 | "unnamed": "^1.2.6" 24 | }, 25 | "devDependencies": { 26 | "prettier": "^1.17.0" 27 | }, 28 | "keywords": [ 29 | "gatsby" 30 | ], 31 | "license": "MIT", 32 | "scripts": { 33 | "build": "gatsby build", 34 | "start": "gatsby develop", 35 | "reset": "rm -rf .cache/ public/", 36 | "format": "prettier --write src/**/*.{js,jsx}", 37 | "serve": "gatsby serve", 38 | "test": "echo \"Write tests! -> https://gatsby.dev/unit-testing\"" 39 | }, 40 | "repository": { 41 | "type": "git", 42 | "url": "https://github.com/gatsbyjs/gatsby-starter-default" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/gatsbyjs/gatsby/issues" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/AddTask.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react' 2 | import axios from 'axios' 3 | import { navigate } from 'gatsby' 4 | import Context from 'components/common/Context' 5 | import SEO from 'components/common/Seo' 6 | 7 | export default () => { 8 | const { dispatch } = useContext(Context) 9 | const [task, setTask] = useState('') 10 | const [isSubmitting, setSubmitting] = useState(false) 11 | const [error, setError] = useState(null) 12 | 13 | const handleChange = e => { 14 | setTask(e.target.value) 15 | } 16 | 17 | const handleBlur = e => { 18 | if (!e.target.value) { 19 | setError('Insert a task!') 20 | } 21 | } 22 | 23 | const handleSubmit = async e => { 24 | e.preventDefault() 25 | setSubmitting(true) 26 | 27 | try { 28 | if (!task) { 29 | setError('Field is required') 30 | } else { 31 | const { data } = await axios.post(`${process.env.API}/post/`, { 32 | title: task, 33 | }) 34 | dispatch({ type: 'Add_NEW_TASK', payload: data }) 35 | navigate('/app/tasks/') 36 | setSubmitting(false) 37 | } 38 | } catch (err) { 39 | setError(err.response.data) 40 | setSubmitting(false) 41 | } 42 | } 43 | 44 | return ( 45 | <> 46 | 47 |
48 |
49 |
50 | 51 | 58 | {error && {error}} 59 |
60 | 67 |
68 |
69 | 70 | ) 71 | } 72 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | import Image from 'components/Image' 4 | import SEO from 'components/common/Seo' 5 | 6 | export default () => ( 7 | <> 8 | 9 |
10 |
11 | 12 |
13 |
14 | 19 | Register 20 | 21 | 22 | Login 23 | 24 |
25 |
26 | 27 | ) 28 | -------------------------------------------------------------------------------- /src/components/AppWrapper.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react' 2 | import axios from 'axios' 3 | import { navigate } from 'gatsby' 4 | import setAuthToken from 'helpers/setAuthToken' 5 | import Layout from 'components/common/Layout' 6 | import Context from './common/Context' 7 | 8 | export default ({ children }) => { 9 | const { user, dispatchUserAction } = useContext(Context) 10 | const [loading, setLoading] = useState(true) 11 | 12 | const fetchUser = async () => { 13 | try { 14 | const token = window.localStorage.getItem('token') 15 | if (token) { 16 | const { data } = await axios({ 17 | method: 'GET', 18 | url: `${process.env.API}/user/verify`, 19 | headers: { 20 | 'Content-Type': 'application/json', 21 | 'x-auth': token, 22 | }, 23 | }) 24 | 25 | await setAuthToken(data.token) 26 | dispatchUserAction({ type: 'SAVE_USER', payload: data.user }) 27 | window.localStorage.setItem('token', data.token) 28 | 29 | if ( 30 | window.location.pathname === '/app/login' || 31 | window.location.pathname === '/app/register' || 32 | window.location.pathname === '/app' || 33 | window.location.pathname === '/app/login/' || 34 | window.location.pathname === '/app/register/' || 35 | window.location.pathname === '/app/' 36 | ) { 37 | navigate('/app/tasks/') 38 | } 39 | setLoading(false) 40 | } else { 41 | if ( 42 | window.location.pathname === '/app/tasks' || 43 | window.location.pathname === '/app/tasks/' || 44 | window.location.pathname === '/app/task' || 45 | window.location.pathname === '/app/task/' || 46 | window.location.pathname === '/app/task/new/' || 47 | window.location.pathname === '/app/task/new' 48 | ) { 49 | navigate('/app/login/') 50 | } 51 | setLoading(false) 52 | } 53 | } catch (err) { 54 | setLoading(false) 55 | } 56 | } 57 | 58 | const logout = async () => { 59 | try { 60 | await axios.delete(`${process.env.API}/user/logout`) 61 | dispatchUserAction({ type: 'LOGOUT' }) 62 | window.localStorage.removeItem('token') 63 | setAuthToken(false) 64 | navigate('/') 65 | } catch (err) { 66 | console.log(err) 67 | } 68 | } 69 | 70 | useEffect(() => { 71 | if (!user.isLoggedIn) { 72 | fetchUser() 73 | } 74 | }, []) 75 | 76 | return ( 77 | <> 78 | {loading ? ( 79 | Loading... 80 | ) : ( 81 | 82 | {children} 83 | 84 | )} 85 | 86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /src/components/Image.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useStaticQuery, graphql } from 'gatsby' 3 | import Img from 'gatsby-image' 4 | 5 | export default () => { 6 | const { placeholderImage } = useStaticQuery(graphql` 7 | query { 8 | placeholderImage: file(relativePath: { eq: "gatsby-astronaut.png" }) { 9 | childImageSharp { 10 | fluid(maxWidth: 300) { 11 | ...GatsbyImageSharpFluid 12 | } 13 | } 14 | } 15 | } 16 | `) 17 | 18 | return 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Form from 'components/common/Form' 3 | import SEO from 'components/common/Seo' 4 | 5 | export default () => ( 6 | <> 7 | 8 |
9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/components/Register.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Form from 'components/common/Form' 3 | import SEO from 'components/common/Seo' 4 | 5 | export default () => ( 6 | <> 7 | 8 | 9 | 10 | ) 11 | -------------------------------------------------------------------------------- /src/components/Task.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import { navigate } from 'gatsby' 3 | import axios from 'axios' 4 | import Context from 'components/common/Context' 5 | import SEO from 'components/common/Seo' 6 | 7 | export default ({ id }) => { 8 | const { tasks, dispatch } = useContext(Context) 9 | const [loading, setLoading] = useState(true) 10 | const [isSubmitting, setSubmitting] = useState(false) 11 | 12 | const fetchTask = async () => { 13 | try { 14 | const { data } = await axios.get(`${process.env.API}/post/${id}`) 15 | dispatch({ type: 'GET_DATA_BY_ID', payload: data }) 16 | setLoading(false) 17 | } catch (err) { 18 | navigate('/404/') 19 | } 20 | } 21 | 22 | const setDone = async () => { 23 | try { 24 | setSubmitting(true) 25 | await axios.patch(`${process.env.API}/post/${id}`, { 26 | isDone: !tasks.isDone, 27 | }) 28 | navigate('/app/tasks/') 29 | setSubmitting(false) 30 | } catch (error) { 31 | // TODO: use react-toastify 32 | alert('something went wrong') 33 | setSubmitting(false) 34 | } 35 | } 36 | 37 | const deleteTask = async () => { 38 | try { 39 | setSubmitting(true) 40 | await axios.delete(`${process.env.API}/post/${id}`) 41 | navigate('/app/tasks/') 42 | setSubmitting(false) 43 | } catch (error) { 44 | // TODO: use react-toastify 45 | alert('something went wrong') 46 | setSubmitting(false) 47 | } 48 | } 49 | 50 | useEffect(() => { 51 | fetchTask() 52 | }, []) 53 | 54 | return ( 55 | <> 56 | 57 | {loading ? ( 58 | Loading... 59 | ) : ( 60 |
61 |

64 | {tasks.title} 65 |

66 | 69 | 72 |
73 | )} 74 | 75 | ) 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Tasks.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useEffect, useState } from 'react' 2 | import axios from 'axios' 3 | import { Link } from 'gatsby' 4 | import Context from 'components/common/Context' 5 | import SEO from 'components/common/Seo' 6 | import TasksLoading from './common/TasksLoading' 7 | 8 | export default () => { 9 | const [loading, setLoading] = useState(true) 10 | const { tasks, dispatch } = useContext(Context) 11 | 12 | const fetchTasks = async () => { 13 | try { 14 | const { data } = await axios.get(`${process.env.API}/post/all`) 15 | 16 | await dispatch({ type: 'FETCH_TASKS', payload: data }) 17 | setLoading(false) 18 | } catch (err) { 19 | setLoading(false) 20 | } 21 | } 22 | 23 | useEffect(() => { 24 | fetchTasks() 25 | }, []) 26 | 27 | return ( 28 | <> 29 | 30 |
31 |

Tasks

32 | {loading ? ( 33 | 34 | ) : ( 35 | <> 36 | {tasks && ( 37 |
    38 | {tasks.map(({ title, _id, isDone }) => ( 39 |
  • 40 | 46 | {title} 47 | 48 |
  • 49 | ))} 50 |
51 | )} 52 | Add new Task 53 | 54 | )} 55 |
56 | 57 | ) 58 | } 59 | -------------------------------------------------------------------------------- /src/components/common/Context.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react' 2 | 3 | const Context = createContext({}) 4 | 5 | export default Context 6 | -------------------------------------------------------------------------------- /src/components/common/Form.js: -------------------------------------------------------------------------------- 1 | import React, { useContext, useState } from 'react' 2 | import axios from 'axios' 3 | import { navigate } from 'gatsby' 4 | import Context from 'components/common/Context' 5 | import setAuthToken from 'helpers/setAuthToken' 6 | 7 | export default ({ form }) => { 8 | const { dispatchUserAction } = useContext(Context) 9 | const [isSubmitting, setSubmitting] = useState(false) 10 | const [details, setDetails] = useState({ 11 | username: '', 12 | email: '', 13 | password: '', 14 | }) 15 | const [errors, setErrors] = useState({ 16 | username: '', 17 | email: '', 18 | password: '', 19 | }) 20 | 21 | const handleChange = e => { 22 | setDetails({ ...details, [e.target.name]: e.target.value }) 23 | } 24 | 25 | const handleBlur = e => { 26 | if (!e.target.value) { 27 | setErrors({ ...errors, [e.target.name]: 'Required field' }) 28 | } 29 | } 30 | 31 | const handleSubmit = async e => { 32 | e.preventDefault() 33 | setSubmitting(true) 34 | 35 | try { 36 | const { username, email, password } = details 37 | 38 | if (form === 'login') { 39 | if (!email || !password) { 40 | setErrors({ 41 | ...errors, 42 | email: 'Field is required', 43 | password: 'Field is required', 44 | }) 45 | } else { 46 | const { data } = await axios.post(`${process.env.API}/user/login`, { 47 | email, 48 | password, 49 | }) 50 | 51 | await setAuthToken(data.token) 52 | dispatchUserAction({ type: 'SAVE_USER', payload: data }) 53 | window.localStorage.setItem('token', data.token) 54 | navigate('/app/tasks/') 55 | } 56 | } else { 57 | if (!username || !email || !password) { 58 | alert('aye sir') 59 | } else { 60 | const { data } = await axios.post( 61 | `${process.env.API}/user/register`, 62 | { 63 | username, 64 | email, 65 | password, 66 | } 67 | ) 68 | 69 | await setAuthToken(data.token) 70 | dispatchUserAction({ type: 'SAVE_USER', payload: data }) 71 | window.localStorage.setItem('token', data.token) 72 | navigate('/app/tasks/') 73 | } 74 | } 75 | } catch (err) { 76 | if (err.response.data.email) { 77 | setErrors({ ...errors, email: err.response.data.email }) 78 | } else if (err.response.data.email) { 79 | setErrors({ ...errors, email: err.response.data.password }) 80 | } else if (err.response.data.email && err.response.data.email) { 81 | setErrors({ 82 | ...errors, 83 | email: err.response.data.email, 84 | password: err.response.data.password, 85 | }) 86 | } else { 87 | setErrors({ 88 | ...errors, 89 | email: err.response.data.error, 90 | }) 91 | } 92 | setSubmitting(false) 93 | } 94 | } 95 | 96 | return ( 97 |
98 | 99 | {form === 'register' && ( 100 |
101 | 102 | 109 | {errors.username && ( 110 | {errors.username} 111 | )} 112 |
113 | )} 114 |
115 | 116 | 123 | {errors.email && {errors.email}} 124 |
125 |
126 | 127 | 134 | {errors.password && ( 135 | {errors.password} 136 | )} 137 |
138 |
139 | 146 |
147 | 148 |
149 | ) 150 | } 151 | -------------------------------------------------------------------------------- /src/components/common/Layout.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { useStaticQuery, graphql } from 'gatsby' 3 | import Header from 'components/theme/Header' 4 | import './layout.css' 5 | import 'unnamed' 6 | 7 | export default ({ children, isLoggedIn, logout }) => { 8 | const { site } = useStaticQuery( 9 | graphql` 10 | query { 11 | site { 12 | siteMetadata { 13 | title 14 | } 15 | } 16 | } 17 | ` 18 | ) 19 | 20 | return ( 21 | <> 22 |
27 |
{children}
28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/components/common/NotFound.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import Layout from 'components/common/Layout' 3 | import SEO from 'components/common/Seo' 4 | 5 | export default () => ( 6 | 7 | 8 |

NOT FOUND

9 |

You just hit a route that doesn't exist... the sadness.

10 |
11 | ) 12 | -------------------------------------------------------------------------------- /src/components/common/Seo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SEO component that queries for data with 3 | * Gatsby's useStaticQuery React hook 4 | * 5 | * See: https://www.gatsbyjs.org/docs/use-static-query/ 6 | */ 7 | 8 | import React from "react" 9 | import PropTypes from "prop-types" 10 | import Helmet from "react-helmet" 11 | import { useStaticQuery, graphql } from "gatsby" 12 | 13 | function SEO({ description, lang, meta, keywords, title }) { 14 | const { site } = useStaticQuery( 15 | graphql` 16 | query { 17 | site { 18 | siteMetadata { 19 | title 20 | description 21 | author 22 | } 23 | } 24 | } 25 | ` 26 | ) 27 | 28 | const metaDescription = description || site.siteMetadata.description 29 | 30 | return ( 31 | 0 73 | ? { 74 | name: `keywords`, 75 | content: keywords.join(`, `), 76 | } 77 | : [] 78 | ) 79 | .concat(meta)} 80 | /> 81 | ) 82 | } 83 | 84 | SEO.defaultProps = { 85 | lang: `en`, 86 | meta: [], 87 | keywords: [], 88 | description: ``, 89 | } 90 | 91 | SEO.propTypes = { 92 | description: PropTypes.string, 93 | lang: PropTypes.string, 94 | meta: PropTypes.arrayOf(PropTypes.object), 95 | keywords: PropTypes.arrayOf(PropTypes.string), 96 | title: PropTypes.string.isRequired, 97 | } 98 | 99 | export default SEO 100 | -------------------------------------------------------------------------------- /src/components/common/TasksLoading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ContentLoader from 'react-content-loader' 3 | 4 | export default () => ( 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ) 22 | -------------------------------------------------------------------------------- /src/components/common/layout.css: -------------------------------------------------------------------------------- 1 | html { 2 | font-family: sans-serif; 3 | -ms-text-size-adjust: 100%; 4 | -webkit-text-size-adjust: 100%; 5 | } 6 | body { 7 | margin: 0; 8 | -webkit-font-smoothing: antialiased; 9 | -moz-osx-font-smoothing: grayscale; 10 | } 11 | article, 12 | aside, 13 | details, 14 | figcaption, 15 | figure, 16 | footer, 17 | header, 18 | main, 19 | menu, 20 | nav, 21 | section, 22 | summary { 23 | display: block; 24 | } 25 | audio, 26 | canvas, 27 | progress, 28 | video { 29 | display: inline-block; 30 | } 31 | audio:not([controls]) { 32 | display: none; 33 | height: 0; 34 | } 35 | progress { 36 | vertical-align: baseline; 37 | } 38 | [hidden], 39 | template { 40 | display: none; 41 | } 42 | a { 43 | background-color: transparent; 44 | -webkit-text-decoration-skip: objects; 45 | } 46 | a:active, 47 | a:hover { 48 | outline-width: 0; 49 | } 50 | abbr[title] { 51 | border-bottom: none; 52 | text-decoration: underline; 53 | text-decoration: underline dotted; 54 | } 55 | b, 56 | strong { 57 | font-weight: inherit; 58 | font-weight: bolder; 59 | } 60 | dfn { 61 | font-style: italic; 62 | } 63 | h1 { 64 | font-size: 2em; 65 | margin: 0.67em 0; 66 | } 67 | mark { 68 | background-color: #ff0; 69 | color: #000; 70 | } 71 | small { 72 | font-size: 80%; 73 | } 74 | sub, 75 | sup { 76 | font-size: 75%; 77 | line-height: 0; 78 | position: relative; 79 | vertical-align: baseline; 80 | } 81 | sub { 82 | bottom: -0.25em; 83 | } 84 | sup { 85 | top: -0.5em; 86 | } 87 | img { 88 | border-style: none; 89 | } 90 | svg:not(:root) { 91 | overflow: hidden; 92 | } 93 | code, 94 | kbd, 95 | pre, 96 | samp { 97 | font-family: monospace, monospace; 98 | font-size: 1em; 99 | } 100 | figure { 101 | margin: 1em 40px; 102 | } 103 | hr { 104 | box-sizing: content-box; 105 | height: 0; 106 | overflow: visible; 107 | } 108 | button, 109 | input, 110 | optgroup, 111 | select, 112 | textarea { 113 | font: inherit; 114 | margin: 0; 115 | } 116 | optgroup { 117 | font-weight: 700; 118 | } 119 | button, 120 | input { 121 | overflow: visible; 122 | } 123 | button, 124 | select { 125 | text-transform: none; 126 | } 127 | [type='reset'], 128 | [type='submit'], 129 | button, 130 | html [type='button'] { 131 | -webkit-appearance: button; 132 | } 133 | [type='button']::-moz-focus-inner, 134 | [type='reset']::-moz-focus-inner, 135 | [type='submit']::-moz-focus-inner, 136 | button::-moz-focus-inner { 137 | border-style: none; 138 | padding: 0; 139 | } 140 | [type='button']:-moz-focusring, 141 | [type='reset']:-moz-focusring, 142 | [type='submit']:-moz-focusring, 143 | button:-moz-focusring { 144 | outline: 1px dotted ButtonText; 145 | } 146 | fieldset { 147 | border: 1px solid silver; 148 | margin: 0 2px; 149 | padding: 0.35em 0.625em 0.75em; 150 | } 151 | legend { 152 | box-sizing: border-box; 153 | color: inherit; 154 | display: table; 155 | max-width: 100%; 156 | padding: 0; 157 | white-space: normal; 158 | } 159 | textarea { 160 | overflow: auto; 161 | } 162 | [type='checkbox'], 163 | [type='radio'] { 164 | box-sizing: border-box; 165 | padding: 0; 166 | } 167 | [type='number']::-webkit-inner-spin-button, 168 | [type='number']::-webkit-outer-spin-button { 169 | height: auto; 170 | } 171 | [type='search'] { 172 | -webkit-appearance: textfield; 173 | outline-offset: -2px; 174 | } 175 | [type='search']::-webkit-search-cancel-button, 176 | [type='search']::-webkit-search-decoration { 177 | -webkit-appearance: none; 178 | } 179 | ::-webkit-input-placeholder { 180 | color: inherit; 181 | opacity: 0.54; 182 | } 183 | ::-webkit-file-upload-button { 184 | -webkit-appearance: button; 185 | font: inherit; 186 | } 187 | html { 188 | font: 112.5%/1.45em georgia, serif; 189 | box-sizing: border-box; 190 | overflow-y: scroll; 191 | } 192 | * { 193 | box-sizing: inherit; 194 | } 195 | *:before { 196 | box-sizing: inherit; 197 | } 198 | *:after { 199 | box-sizing: inherit; 200 | } 201 | body { 202 | color: hsla(0, 0%, 0%, 0.8); 203 | font-family: georgia, serif; 204 | font-weight: normal; 205 | word-wrap: break-word; 206 | font-kerning: normal; 207 | -moz-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 208 | -ms-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 209 | -webkit-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 210 | font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 211 | } 212 | img { 213 | max-width: 100%; 214 | margin-left: 0; 215 | margin-right: 0; 216 | margin-top: 0; 217 | padding-bottom: 0; 218 | padding-left: 0; 219 | padding-right: 0; 220 | padding-top: 0; 221 | margin-bottom: 1.45rem; 222 | } 223 | h1 { 224 | margin-left: 0; 225 | margin-right: 0; 226 | margin-top: 0; 227 | padding-bottom: 0; 228 | padding-left: 0; 229 | padding-right: 0; 230 | padding-top: 0; 231 | margin-bottom: 1.45rem; 232 | color: inherit; 233 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 234 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 235 | font-weight: bold; 236 | text-rendering: optimizeLegibility; 237 | font-size: 2.25rem; 238 | line-height: 1.1; 239 | } 240 | h2 { 241 | margin-left: 0; 242 | margin-right: 0; 243 | margin-top: 0; 244 | padding-bottom: 0; 245 | padding-left: 0; 246 | padding-right: 0; 247 | padding-top: 0; 248 | margin-bottom: 1.45rem; 249 | color: inherit; 250 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 251 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 252 | font-weight: bold; 253 | text-rendering: optimizeLegibility; 254 | font-size: 1.62671rem; 255 | line-height: 1.1; 256 | } 257 | h3 { 258 | margin-left: 0; 259 | margin-right: 0; 260 | margin-top: 0; 261 | padding-bottom: 0; 262 | padding-left: 0; 263 | padding-right: 0; 264 | padding-top: 0; 265 | margin-bottom: 1.45rem; 266 | color: inherit; 267 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 268 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 269 | font-weight: bold; 270 | text-rendering: optimizeLegibility; 271 | font-size: 1.38316rem; 272 | line-height: 1.1; 273 | } 274 | h4 { 275 | margin-left: 0; 276 | margin-right: 0; 277 | margin-top: 0; 278 | padding-bottom: 0; 279 | padding-left: 0; 280 | padding-right: 0; 281 | padding-top: 0; 282 | margin-bottom: 1.45rem; 283 | color: inherit; 284 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 285 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 286 | font-weight: bold; 287 | text-rendering: optimizeLegibility; 288 | font-size: 1rem; 289 | line-height: 1.1; 290 | } 291 | h5 { 292 | margin-left: 0; 293 | margin-right: 0; 294 | margin-top: 0; 295 | padding-bottom: 0; 296 | padding-left: 0; 297 | padding-right: 0; 298 | padding-top: 0; 299 | margin-bottom: 1.45rem; 300 | color: inherit; 301 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 302 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 303 | font-weight: bold; 304 | text-rendering: optimizeLegibility; 305 | font-size: 0.85028rem; 306 | line-height: 1.1; 307 | } 308 | h6 { 309 | margin-left: 0; 310 | margin-right: 0; 311 | margin-top: 0; 312 | padding-bottom: 0; 313 | padding-left: 0; 314 | padding-right: 0; 315 | padding-top: 0; 316 | margin-bottom: 1.45rem; 317 | color: inherit; 318 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 319 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 320 | font-weight: bold; 321 | text-rendering: optimizeLegibility; 322 | font-size: 0.78405rem; 323 | line-height: 1.1; 324 | } 325 | hgroup { 326 | margin-left: 0; 327 | margin-right: 0; 328 | margin-top: 0; 329 | padding-bottom: 0; 330 | padding-left: 0; 331 | padding-right: 0; 332 | padding-top: 0; 333 | margin-bottom: 1.45rem; 334 | } 335 | ul { 336 | margin-left: 1.45rem; 337 | margin-right: 0; 338 | margin-top: 0; 339 | padding-bottom: 0; 340 | padding-left: 0; 341 | padding-right: 0; 342 | padding-top: 0; 343 | margin-bottom: 1.45rem; 344 | list-style-position: outside; 345 | list-style-image: none; 346 | } 347 | ol { 348 | margin-left: 1.45rem; 349 | margin-right: 0; 350 | margin-top: 0; 351 | padding-bottom: 0; 352 | padding-left: 0; 353 | padding-right: 0; 354 | padding-top: 0; 355 | margin-bottom: 1.45rem; 356 | list-style-position: outside; 357 | list-style-image: none; 358 | } 359 | dl { 360 | margin-left: 0; 361 | margin-right: 0; 362 | margin-top: 0; 363 | padding-bottom: 0; 364 | padding-left: 0; 365 | padding-right: 0; 366 | padding-top: 0; 367 | margin-bottom: 1.45rem; 368 | } 369 | dd { 370 | margin-left: 0; 371 | margin-right: 0; 372 | margin-top: 0; 373 | padding-bottom: 0; 374 | padding-left: 0; 375 | padding-right: 0; 376 | padding-top: 0; 377 | margin-bottom: 1.45rem; 378 | } 379 | p { 380 | margin-left: 0; 381 | margin-right: 0; 382 | margin-top: 0; 383 | padding-bottom: 0; 384 | padding-left: 0; 385 | padding-right: 0; 386 | padding-top: 0; 387 | margin-bottom: 1.45rem; 388 | } 389 | figure { 390 | margin-left: 0; 391 | margin-right: 0; 392 | margin-top: 0; 393 | padding-bottom: 0; 394 | padding-left: 0; 395 | padding-right: 0; 396 | padding-top: 0; 397 | margin-bottom: 1.45rem; 398 | } 399 | pre { 400 | margin-left: 0; 401 | margin-right: 0; 402 | margin-top: 0; 403 | margin-bottom: 1.45rem; 404 | font-size: 0.85rem; 405 | line-height: 1.42; 406 | background: hsla(0, 0%, 0%, 0.04); 407 | border-radius: 3px; 408 | overflow: auto; 409 | word-wrap: normal; 410 | padding: 1.45rem; 411 | } 412 | table { 413 | margin-left: 0; 414 | margin-right: 0; 415 | margin-top: 0; 416 | padding-bottom: 0; 417 | padding-left: 0; 418 | padding-right: 0; 419 | padding-top: 0; 420 | margin-bottom: 1.45rem; 421 | font-size: 1rem; 422 | line-height: 1.45rem; 423 | border-collapse: collapse; 424 | width: 100%; 425 | } 426 | fieldset { 427 | margin-left: 0; 428 | margin-right: 0; 429 | margin-top: 0; 430 | padding-bottom: 0; 431 | padding-left: 0; 432 | padding-right: 0; 433 | padding-top: 0; 434 | margin-bottom: 1.45rem; 435 | } 436 | blockquote { 437 | margin-left: 1.45rem; 438 | margin-right: 1.45rem; 439 | margin-top: 0; 440 | padding-bottom: 0; 441 | padding-left: 0; 442 | padding-right: 0; 443 | padding-top: 0; 444 | margin-bottom: 1.45rem; 445 | } 446 | form { 447 | margin-left: 0; 448 | margin-right: 0; 449 | margin-top: 0; 450 | padding-bottom: 0; 451 | padding-left: 0; 452 | padding-right: 0; 453 | padding-top: 0; 454 | margin-bottom: 1.45rem; 455 | } 456 | noscript { 457 | margin-left: 0; 458 | margin-right: 0; 459 | margin-top: 0; 460 | padding-bottom: 0; 461 | padding-left: 0; 462 | padding-right: 0; 463 | padding-top: 0; 464 | margin-bottom: 1.45rem; 465 | } 466 | iframe { 467 | margin-left: 0; 468 | margin-right: 0; 469 | margin-top: 0; 470 | padding-bottom: 0; 471 | padding-left: 0; 472 | padding-right: 0; 473 | padding-top: 0; 474 | margin-bottom: 1.45rem; 475 | } 476 | hr { 477 | margin-left: 0; 478 | margin-right: 0; 479 | margin-top: 0; 480 | padding-bottom: 0; 481 | padding-left: 0; 482 | padding-right: 0; 483 | padding-top: 0; 484 | margin-bottom: calc(1.45rem - 1px); 485 | background: hsla(0, 0%, 0%, 0.2); 486 | border: none; 487 | height: 1px; 488 | } 489 | address { 490 | margin-left: 0; 491 | margin-right: 0; 492 | margin-top: 0; 493 | padding-bottom: 0; 494 | padding-left: 0; 495 | padding-right: 0; 496 | padding-top: 0; 497 | margin-bottom: 1.45rem; 498 | } 499 | b { 500 | font-weight: bold; 501 | } 502 | strong { 503 | font-weight: bold; 504 | } 505 | dt { 506 | font-weight: bold; 507 | } 508 | th { 509 | font-weight: bold; 510 | } 511 | li { 512 | margin-bottom: calc(1.45rem / 2); 513 | } 514 | ol li { 515 | padding-left: 0; 516 | } 517 | ul li { 518 | padding-left: 0; 519 | } 520 | li > ol { 521 | margin-left: 1.45rem; 522 | margin-bottom: calc(1.45rem / 2); 523 | margin-top: calc(1.45rem / 2); 524 | } 525 | li > ul { 526 | margin-left: 1.45rem; 527 | margin-bottom: calc(1.45rem / 2); 528 | margin-top: calc(1.45rem / 2); 529 | } 530 | blockquote *:last-child { 531 | margin-bottom: 0; 532 | } 533 | li *:last-child { 534 | margin-bottom: 0; 535 | } 536 | p *:last-child { 537 | margin-bottom: 0; 538 | } 539 | li > p { 540 | margin-bottom: calc(1.45rem / 2); 541 | } 542 | code { 543 | font-size: 0.85rem; 544 | line-height: 1.45rem; 545 | } 546 | kbd { 547 | font-size: 0.85rem; 548 | line-height: 1.45rem; 549 | } 550 | samp { 551 | font-size: 0.85rem; 552 | line-height: 1.45rem; 553 | } 554 | abbr { 555 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 556 | cursor: help; 557 | } 558 | acronym { 559 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 560 | cursor: help; 561 | } 562 | abbr[title] { 563 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 564 | cursor: help; 565 | text-decoration: none; 566 | } 567 | thead { 568 | text-align: left; 569 | } 570 | td, 571 | th { 572 | text-align: left; 573 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 574 | font-feature-settings: 'tnum'; 575 | -moz-font-feature-settings: 'tnum'; 576 | -ms-font-feature-settings: 'tnum'; 577 | -webkit-font-feature-settings: 'tnum'; 578 | padding-left: 0.96667rem; 579 | padding-right: 0.96667rem; 580 | padding-top: 0.725rem; 581 | padding-bottom: calc(0.725rem - 1px); 582 | } 583 | th:first-child, 584 | td:first-child { 585 | padding-left: 0; 586 | } 587 | th:last-child, 588 | td:last-child { 589 | padding-right: 0; 590 | } 591 | tt, 592 | code { 593 | background-color: hsla(0, 0%, 0%, 0.04); 594 | border-radius: 3px; 595 | font-family: 'SFMono-Regular', Consolas, 'Roboto Mono', 'Droid Sans Mono', 596 | 'Liberation Mono', Menlo, Courier, monospace; 597 | padding: 0; 598 | padding-top: 0.2em; 599 | padding-bottom: 0.2em; 600 | } 601 | pre code { 602 | background: none; 603 | line-height: 1.42; 604 | } 605 | code:before, 606 | code:after, 607 | tt:before, 608 | tt:after { 609 | letter-spacing: -0.2em; 610 | content: ' '; 611 | } 612 | pre code:before, 613 | pre code:after, 614 | pre tt:before, 615 | pre tt:after { 616 | content: ''; 617 | } 618 | @media only screen and (max-width: 480px) { 619 | html { 620 | font-size: 100%; 621 | } 622 | } 623 | 624 | .lock-icon:before { 625 | content: ''; 626 | } 627 | 628 | .task-icon:before { 629 | content: ''; 630 | } 631 | 632 | .container .input-field span:before { 633 | top: 3.4rem; 634 | } 635 | -------------------------------------------------------------------------------- /src/components/theme/Header.js: -------------------------------------------------------------------------------- 1 | import { Link } from 'gatsby' 2 | import React from 'react' 3 | 4 | export default ({ siteTitle, isLoggedIn, logout }) => ( 5 |
12 |
13 |

14 | 21 | {siteTitle} 22 | 23 |

24 | {isLoggedIn && ( 25 | 32 | )} 33 |
34 |
35 | ) 36 | -------------------------------------------------------------------------------- /src/helpers/setAuthToken.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export default token => { 4 | if (token) { 5 | axios.defaults.headers.common['x-auth'] = token 6 | } else { 7 | delete axios.defaults.headers.common['x-auth'] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/images/gatsby-astronaut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smakosh/gatsby-app-starter-rest-api/8e83681ea81d3d1671048cc2079fd36341c7cc42/src/images/gatsby-astronaut.png -------------------------------------------------------------------------------- /src/images/gatsby-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/smakosh/gatsby-app-starter-rest-api/8e83681ea81d3d1671048cc2079fd36341c7cc42/src/images/gatsby-icon.png -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import NotFound from 'components/common/NotFound' 3 | 4 | export default () => 5 | -------------------------------------------------------------------------------- /src/pages/app.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Router } from '@reach/router' 3 | import Provider from 'providers/Provider' 4 | import AppWrapper from 'components/AppWrapper' 5 | import App from 'components/App' 6 | import Tasks from 'components/Tasks' 7 | import Task from 'components/Task' 8 | import AddTask from 'components/AddTask' 9 | import NotFound from 'components/common/NotFound' 10 | import Register from 'components/Register' 11 | import Login from 'components/Login' 12 | 13 | export default () => ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'gatsby' 3 | import Layout from 'components/common/Layout' 4 | import SEO from 'components/common/Seo' 5 | 6 | export default () => ( 7 | 8 | 9 |
10 |

This could be the landing page

11 | Navigate to the app 12 |
13 |
14 | ) 15 | -------------------------------------------------------------------------------- /src/providers/Provider.js: -------------------------------------------------------------------------------- 1 | import React, { useReducer } from 'react' 2 | import Context from 'components/common/Context' 3 | import reducer from './taskReducer' 4 | import userReducer from './userReducer' 5 | 6 | export default ({ children }) => { 7 | const [tasks, dispatch] = useReducer(reducer, []) 8 | const [user, dispatchUserAction] = useReducer(userReducer, {}) 9 | 10 | return ( 11 | 19 | {children} 20 | 21 | ) 22 | } 23 | -------------------------------------------------------------------------------- /src/providers/taskReducer.js: -------------------------------------------------------------------------------- 1 | import { navigate } from 'gatsby' 2 | 3 | export default (tasks, action) => { 4 | switch (action.type) { 5 | case 'Add_NEW_TASK': 6 | return [...tasks, action.payload] 7 | case 'FETCH_TASKS': 8 | return action.payload 9 | case 'GET_DATA_BY_ID': 10 | return action.payload 11 | case 'TOGGLE_TASK': 12 | navigate('/app/') 13 | return tasks.map(item => 14 | item.id === action.id 15 | ? { 16 | ...item, 17 | isDone: !item.isDone, 18 | } 19 | : item 20 | ) 21 | default: 22 | return tasks 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/providers/userReducer.js: -------------------------------------------------------------------------------- 1 | export default (user, action) => { 2 | switch (action.type) { 3 | case 'SAVE_USER': 4 | return { 5 | ...user, 6 | isLoggedIn: true, 7 | data: action.payload, 8 | } 9 | case 'LOGOUT': 10 | return { 11 | ...user, 12 | isLoggedIn: false, 13 | data: {}, 14 | } 15 | default: 16 | return user 17 | } 18 | } 19 | --------------------------------------------------------------------------------