├── src ├── assets │ └── .gitkeep ├── app │ ├── models │ │ ├── index.ts │ │ └── todo.ts │ ├── layout │ │ ├── index.tsx │ │ └── Base │ │ │ └── index.tsx │ ├── components │ │ ├── index.tsx │ │ └── AppBar │ │ │ └── index.tsx │ ├── app.tsx │ ├── pages │ │ ├── Todos │ │ │ ├── List │ │ │ │ ├── components │ │ │ │ │ ├── List │ │ │ │ │ │ └── index.tsx │ │ │ │ │ ├── AddTodoForm │ │ │ │ │ │ └── index.tsx │ │ │ │ │ └── TodoItem │ │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ └── Edit │ │ │ │ └── index.tsx │ │ └── Auth │ │ │ └── SignIn │ │ │ └── index.tsx │ └── Router.tsx ├── styles.scss └── main.tsx ├── .prettierrc ├── e2e ├── src │ ├── support │ │ ├── app.po.ts │ │ ├── e2e.ts │ │ └── commands.ts │ ├── fixtures │ │ └── example.json │ └── e2e │ │ └── app.cy.ts ├── cypress.config.ts ├── tsconfig.json ├── .eslintrc.json └── project.json ├── api ├── routes.json └── db.json ├── public └── favicon.ico ├── .prettierignore ├── proxy.conf.json ├── .vscode ├── extensions.json └── settings.json ├── README.md ├── .eslintrc.json ├── index.html ├── tsconfig.spec.json ├── tsconfig.app.json ├── .gitignore ├── tsconfig.json ├── vite.config.ts ├── nx.json ├── package.json └── project.json /src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /src/app/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './todo'; 2 | -------------------------------------------------------------------------------- /e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getGreeting = () => cy.get('h1'); 2 | -------------------------------------------------------------------------------- /src/app/layout/index.tsx: -------------------------------------------------------------------------------- 1 | export { default as BaseLayout } from './Base'; 2 | -------------------------------------------------------------------------------- /api/routes.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/*": "/$1", 3 | "users": 600, 4 | "tasks": 644 5 | } -------------------------------------------------------------------------------- /src/app/components/index.tsx: -------------------------------------------------------------------------------- 1 | 2 | export { default as AppBar } from './AppBar'; 3 | 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Puppo/learning-react-query/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | 3 | /dist 4 | /coverage 5 | -------------------------------------------------------------------------------- /proxy.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api": { 3 | "target": "http://localhost:3001", 4 | "secure": false 5 | } 6 | } -------------------------------------------------------------------------------- /src/app/models/todo.ts: -------------------------------------------------------------------------------- 1 | export interface Todo { 2 | id: number; 3 | text: string; 4 | assigneeId?: number; 5 | } 6 | -------------------------------------------------------------------------------- /e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning React Query 2 | 3 | This repo contains the code of my videos about react query. 4 | 5 | The application is a simple to-do app to see all the features of react query. 6 | -------------------------------------------------------------------------------- /api/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "email": "test@test.it", 5 | "password": "$2a$10$ONSI/ac5n4iyMj7LfcRIsOzYQ8dqPYK33TTH1Qp7YORPjvofkoTgS", 6 | "id": 1 7 | } 8 | ], 9 | "tasks": [] 10 | } -------------------------------------------------------------------------------- /e2e/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | import { nxE2EPreset } from '@nrwl/cypress/plugins/cypress-preset'; 3 | 4 | export default defineConfig({ 5 | e2e: nxE2EPreset(__dirname, { 6 | bundler: 'vite', 7 | }), 8 | }); 9 | -------------------------------------------------------------------------------- /e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"] 8 | }, 9 | "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | body { 4 | all: unset; 5 | } 6 | 7 | .error { 8 | display: none; 9 | } 10 | .error.active { 11 | display: block; 12 | color: red; 13 | } 14 | 15 | .fade-out { 16 | visibility: hidden; 17 | opacity: 0; 18 | transition: visibility 1s 0.5s, opacity 0.5s; 19 | } 20 | -------------------------------------------------------------------------------- /src/app/app.tsx: -------------------------------------------------------------------------------- 1 | import { SnackbarProvider } from 'notistack'; 2 | import React from "react"; 3 | import Router from './Router'; 4 | 5 | function App() { 6 | 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | 15 | } 16 | 17 | export default App; 18 | -------------------------------------------------------------------------------- /src/app/layout/Base/index.tsx: -------------------------------------------------------------------------------- 1 | import { PropsWithChildren } from "react"; 2 | import { AppBar } from "../../components"; 3 | 4 | export default function ( 5 | { children }: PropsWithChildren 6 | ) { 7 | const auth = false; 8 | 9 | const handleLogout = () => { 10 | // TODO handle logout 11 | } 12 | 13 | return <> 14 | 18 | {children} 19 | 20 | } -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:@nrwl/nx/react"], 3 | "root": true, 4 | "ignorePatterns": ["!**/*"], 5 | "plugins": ["@nrwl/nx"], 6 | "overrides": [ 7 | { 8 | "files": ["*.ts", "*.tsx"], 9 | "extends": ["plugin:@nrwl/nx/typescript"], 10 | "rules": {} 11 | }, 12 | { 13 | "files": ["*.js", "*.jsx"], 14 | "extends": ["plugin:@nrwl/nx/javascript"], 15 | "rules": {} 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /e2e/src/e2e/app.cy.ts: -------------------------------------------------------------------------------- 1 | import { getGreeting } from '../support/app.po'; 2 | 3 | describe('todo-list', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display welcome message', () => { 7 | // Custom command example, see `../support/commands.ts` file 8 | cy.login('my-email@something.com', 'myPassword'); 9 | 10 | // Function helper example, see `../support/app.po.ts` file 11 | getGreeting().contains('Welcome todo-list'); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "workbench.colorCustomizations": { 3 | "activityBar.activeBackground": "#3399ff", 4 | "activityBar.background": "#3399ff", 5 | "sash.hoverBorder": "#3399ff", 6 | "statusBar.background": "#007fff", 7 | "statusBarItem.hoverBackground": "#3399ff", 8 | "statusBarItem.remoteBackground": "#007fff", 9 | "titleBar.activeBackground": "#007fff", 10 | "titleBar.inactiveBackground": "#007fff99" 11 | }, 12 | "peacock.color": "#007fff" 13 | } 14 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import * as ReactDOM from 'react-dom/client'; 3 | 4 | import '@fontsource/roboto/300.css'; 5 | import '@fontsource/roboto/400.css'; 6 | import '@fontsource/roboto/500.css'; 7 | import '@fontsource/roboto/700.css'; 8 | 9 | import App from './app/App'; 10 | 11 | const root = ReactDOM.createRoot( 12 | document.getElementById('root') as HTMLElement 13 | ); 14 | root.render( 15 | 16 | 17 | 18 | ); 19 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | TodoList 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": ["vitest/globals", "node"] 6 | }, 7 | "include": [ 8 | "vite.config.ts", 9 | "src/**/*.test.ts", 10 | "src/**/*.spec.ts", 11 | "src/**/*.test.tsx", 12 | "src/**/*.spec.tsx", 13 | "src/**/*.test.js", 14 | "src/**/*.spec.js", 15 | "src/**/*.test.jsx", 16 | "src/**/*.spec.jsx", 17 | "src/**/*.d.ts" 18 | ], 19 | "files": [ 20 | "./node_modules/@nrwl/react/typings/cssmodule.d.ts", 21 | "./node_modules/@nrwl/react/typings/image.d.ts" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist/out-tsc", 5 | "types": ["node"] 6 | }, 7 | "files": [ 8 | "./node_modules/@nrwl/react/typings/cssmodule.d.ts", 9 | "./node_modules/@nrwl/react/typings/image.d.ts" 10 | ], 11 | "exclude": [ 12 | "src/**/*.spec.ts", 13 | "src/**/*.test.ts", 14 | "src/**/*.spec.tsx", 15 | "src/**/*.test.tsx", 16 | "src/**/*.spec.js", 17 | "src/**/*.test.js", 18 | "src/**/*.spec.jsx", 19 | "src/**/*.test.jsx" 20 | ], 21 | "include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"] 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /e2e/src/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /e2e/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["plugin:cypress/recommended"], 3 | "ignorePatterns": ["!**/*"], 4 | "overrides": [ 5 | { 6 | "files": ["*.ts", "*.tsx"], 7 | "extends": ["plugin:@nrwl/nx/typescript"], 8 | "rules": {} 9 | }, 10 | { 11 | "files": ["*.js", "*.jsx"], 12 | "extends": ["plugin:@nrwl/nx/javascript"], 13 | "rules": {} 14 | }, 15 | { 16 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 17 | "rules": {} 18 | }, 19 | { 20 | "files": ["src/plugins/index.js"], 21 | "rules": { 22 | "@typescript-eslint/no-var-requires": "off", 23 | "no-undef": "off" 24 | } 25 | } 26 | ], 27 | "plugins": ["@nrwl/nx"] 28 | } 29 | -------------------------------------------------------------------------------- /e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "e2e", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "e2e/src", 5 | "projectType": "application", 6 | "targets": { 7 | "e2e": { 8 | "executor": "@nrwl/cypress:cypress", 9 | "options": { 10 | "cypressConfig": "e2e/cypress.config.ts", 11 | "devServerTarget": "todo-list:serve:development", 12 | "testingType": "e2e" 13 | }, 14 | "configurations": { 15 | "production": { 16 | "devServerTarget": "todo-list:serve:production" 17 | } 18 | } 19 | }, 20 | "lint": { 21 | "executor": "@nrwl/linter:eslint", 22 | "outputs": ["{options.outputFile}"], 23 | "options": { 24 | "lintFilePatterns": ["e2e/**/*.{js,ts}"] 25 | } 26 | } 27 | }, 28 | "tags": [], 29 | "implicitDependencies": ["todo-list"] 30 | } 31 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": ".", 4 | "sourceMap": true, 5 | "declaration": false, 6 | "moduleResolution": "node", 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "importHelpers": true, 10 | "target": "es2015", 11 | "module": "esnext", 12 | "lib": ["es2017", "dom"], 13 | "skipLibCheck": true, 14 | "skipDefaultLibCheck": true, 15 | "baseUrl": ".", 16 | "jsx": "react-jsx", 17 | "allowJs": false, 18 | "esModuleInterop": false, 19 | "allowSyntheticDefaultImports": true, 20 | "strict": true, 21 | "types": ["vite/client", "vitest"] 22 | }, 23 | "files": [], 24 | "include": [], 25 | "references": [ 26 | { 27 | "path": "./tsconfig.app.json" 28 | }, 29 | { 30 | "path": "./tsconfig.spec.json" 31 | } 32 | ], 33 | "compileOnSave": false, 34 | "exclude": ["node_modules", "tmp"] 35 | } 36 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | import { defineConfig } from 'vite'; 3 | import react from '@vitejs/plugin-react'; 4 | import viteTsConfigPaths from 'vite-tsconfig-paths'; 5 | 6 | export default defineConfig({ 7 | cacheDir: './node_modules/.vite/todo-list', 8 | 9 | server: { 10 | port: 4200, 11 | host: 'localhost', 12 | }, 13 | 14 | preview: { 15 | port: 4300, 16 | host: 'localhost', 17 | }, 18 | 19 | plugins: [ 20 | react(), 21 | viteTsConfigPaths({ 22 | root: './', 23 | }), 24 | ], 25 | 26 | // Uncomment this if you are using workers. 27 | // worker: { 28 | // plugins: [ 29 | // viteTsConfigPaths({ 30 | // root: './', 31 | // }), 32 | // ], 33 | // }, 34 | 35 | test: { 36 | globals: true, 37 | cache: { 38 | dir: './node_modules/.vitest', 39 | }, 40 | environment: 'jsdom', 41 | include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /src/app/pages/Todos/List/components/List/index.tsx: -------------------------------------------------------------------------------- 1 | import { Grid } from "@mui/material"; 2 | import { useSnackbar } from "notistack"; 3 | import { Todo } from "../../../../../models"; 4 | import TodoItem from "../TodoItem"; 5 | 6 | interface ListProps { 7 | auth: boolean, 8 | list: Todo[] 9 | } 10 | 11 | function List({ 12 | auth, 13 | list, 14 | }: ListProps) { 15 | const { enqueueSnackbar } = useSnackbar(); 16 | 17 | const deleteTodo = (todo: Todo) => { 18 | // TODO: add todo 19 | enqueueSnackbar('Delete Todo', { 20 | variant: 'success' 21 | }) 22 | }; 23 | 24 | const assignToMe = (todo: Todo) => { 25 | // TODO: add todo 26 | enqueueSnackbar('Assign Todo to you', { 27 | variant: 'success' 28 | }) 29 | }; 30 | 31 | 32 | return 33 | {list.map(todo => 34 | deleteTodo(todo)} 39 | assignToMe={() => assignToMe(todo)} 40 | />)} 41 | 42 | 43 | } 44 | 45 | export default List; 46 | -------------------------------------------------------------------------------- /src/app/Router.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | createBrowserRouter, 4 | RouterProvider 5 | } from "react-router-dom"; 6 | 7 | import { BaseLayout } from './layout'; 8 | 9 | const ListPage = React.lazy(() => import('./pages/Todos/List')) 10 | const EditTodoPage = React.lazy(() => import('./pages/Todos/Edit')) 11 | const SignInPage = React.lazy(() => import('./pages/Auth/SignIn')) 12 | 13 | 14 | 15 | const router = createBrowserRouter([ 16 | { 17 | path: "/", 18 | element: 19 | 20 | 21 | 22 | 23 | 24 | , 25 | }, 26 | { 27 | path: "/todos/:id", 28 | element: 29 | 30 | 31 | 32 | , 33 | }, { 34 | path: "/auth/sign-in", 35 | element: 36 | 37 | 38 | 39 | , 40 | }, 41 | ]); 42 | 43 | 44 | 45 | export default function () { 46 | 47 | 48 | return 49 | } -------------------------------------------------------------------------------- /e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /src/app/pages/Auth/SignIn/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Grid, Paper, TextField, Typography } from "@mui/material"; 2 | 3 | const styles = { 4 | Paper: { 5 | padding: 20, 6 | margin: "auto", 7 | textAlign: "center", 8 | width: 500 9 | } 10 | } as const; 11 | 12 | function SignInPage() { 13 | 14 | const onSignIn = () => { 15 | // TODO: handle sign-in 16 | } 17 | 18 | return ( 19 | 20 | 21 | 22 | 24 | Sign In 25 | 26 | 32 | 35 | 36 | 40 | 41 | 45 | 46 | 47 | 48 | 49 | ); 50 | 51 | } 52 | 53 | export default SignInPage; 54 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "npmScope": "todo-list", 4 | "tasksRunnerOptions": { 5 | "default": { 6 | "runner": "nx/tasks-runners/default", 7 | "options": { 8 | "cacheableOperations": ["build", "lint", "test", "e2e"] 9 | } 10 | } 11 | }, 12 | "targetDefaults": { 13 | "build": { 14 | "dependsOn": ["^build"], 15 | "inputs": ["production", "^production"] 16 | }, 17 | "e2e": { 18 | "inputs": ["default", "^production"] 19 | }, 20 | "test": { 21 | "inputs": ["default", "^production"] 22 | }, 23 | "lint": { 24 | "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] 25 | } 26 | }, 27 | "namedInputs": { 28 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 29 | "production": [ 30 | "default", 31 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 32 | "!{projectRoot}/tsconfig.spec.json", 33 | "!{projectRoot}/.eslintrc.json" 34 | ], 35 | "sharedGlobals": [] 36 | }, 37 | "generators": { 38 | "@nrwl/react": { 39 | "application": { 40 | "style": "scss", 41 | "linter": "eslint", 42 | "bundler": "vite", 43 | "babel": true 44 | }, 45 | "component": { 46 | "style": "scss" 47 | }, 48 | "library": { 49 | "style": "scss", 50 | "linter": "eslint" 51 | } 52 | } 53 | }, 54 | "defaultProject": "todo-list" 55 | } 56 | -------------------------------------------------------------------------------- /src/app/pages/Todos/Edit/index.tsx: -------------------------------------------------------------------------------- 1 | import { Save } from "@mui/icons-material"; 2 | import { Box, Grid, Paper } from "@mui/material"; 3 | import IconButton from "@mui/material/IconButton"; 4 | import Input from "@mui/material/Input"; 5 | import { useRef, useState } from "react"; 6 | import { useParams } from "react-router-dom"; 7 | import { Todo } from "../../../models"; 8 | 9 | const styles = { 10 | Icon: { 11 | marginLeft: "auto", 12 | width: "10%" 13 | }, 14 | Paper: { 15 | margin: "auto", 16 | padding: 10, 17 | alignItems: "center", 18 | marginTop: 10, 19 | width: 500 20 | } 21 | } as const; 22 | 23 | function EditTodo() { 24 | const inputRef = useRef(null); 25 | 26 | const { id } = useParams() 27 | const [todo] = useState(null) 28 | 29 | const onSubmit: React.FormEventHandler = () => { 30 | if (!inputRef.current) return; 31 | 32 | // TODO: update todo 33 | } 34 | 35 | return ( 36 | 37 | 38 | 45 | 51 | 57 | 58 | 59 | 60 | 61 | 62 | ); 63 | } 64 | 65 | export default EditTodo; 66 | -------------------------------------------------------------------------------- /src/app/pages/Todos/List/components/AddTodoForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { TextField } from "@mui/material"; 2 | import Button from "@mui/material/Button"; 3 | import { Box } from "@mui/system"; 4 | import React, { useCallback, useRef } from "react"; 5 | 6 | interface AddTodoFormProps { 7 | addToList: (text: string) => void; 8 | } 9 | 10 | function AddTodoForm({ addToList }: AddTodoFormProps) { 11 | const inputRef = useRef(null); 12 | const errorRef = useRef(null); 13 | 14 | const handleSubmit: React.FormEventHandler = useCallback((e) => { 15 | e.preventDefault(); 16 | if (!inputRef.current) return; 17 | if (!errorRef.current) return; 18 | if (inputRef.current.value === "") { 19 | errorRef.current.classList.add("active"); 20 | return null; 21 | } 22 | errorRef.current.classList.remove("active"); 23 | addToList(inputRef.current.value); 24 | e.currentTarget.reset(); 25 | }, [inputRef, errorRef, addToList]); 26 | 27 | return ( 28 | 29 | 33 | 44 | 45 | 53 | 54 | 55 | 56 |

57 | Error, must enter a value! 58 |

59 |
60 |
61 | ); 62 | } 63 | 64 | export default AddTodoForm -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-list", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "start": "nx serve", 7 | "build": "nx build", 8 | "test": "nx test", 9 | "api": "json-server-auth --watch api/db.json --port 3001 --routes api/routes.json" 10 | }, 11 | "private": true, 12 | "devDependencies": { 13 | "@nrwl/cypress": "^15.6.3", 14 | "@nrwl/eslint-plugin-nx": "15.6.3", 15 | "@nrwl/linter": "15.6.3", 16 | "@nrwl/react": "15.6.3", 17 | "@nrwl/vite": "15.6.3", 18 | "@nrwl/workspace": "15.6.3", 19 | "@testing-library/react": "13.4.0", 20 | "@types/node": "18.11.9", 21 | "@types/react": "18.0.25", 22 | "@types/react-dom": "18.0.9", 23 | "@typescript-eslint/eslint-plugin": "^5.36.1", 24 | "@typescript-eslint/parser": "^5.36.1", 25 | "@vitejs/plugin-react": "^3.0.0", 26 | "@vitest/coverage-c8": "~0.25.8", 27 | "@vitest/ui": "^0.25.8", 28 | "cypress": "^12.2.0", 29 | "eslint": "~8.15.0", 30 | "eslint-config-prettier": "8.1.0", 31 | "eslint-plugin-cypress": "^2.10.3", 32 | "eslint-plugin-import": "2.26.0", 33 | "eslint-plugin-jsx-a11y": "6.6.1", 34 | "eslint-plugin-react": "7.31.11", 35 | "eslint-plugin-react-hooks": "4.6.0", 36 | "jsdom": "~20.0.3", 37 | "json-server": "^0.17.1", 38 | "json-server-auth": "^2.1.0", 39 | "nx": "15.6.3", 40 | "prettier": "^2.6.2", 41 | "react-test-renderer": "18.2.0", 42 | "sass": "^1.55.0", 43 | "typescript": "~4.8.2", 44 | "vite": "^4.0.1", 45 | "vite-plugin-eslint": "^1.8.1", 46 | "vite-tsconfig-paths": "^4.0.2", 47 | "vitest": "^0.25.8" 48 | }, 49 | "dependencies": { 50 | "@emotion/react": "^11.10.5", 51 | "@emotion/styled": "^11.10.5", 52 | "@fontsource/roboto": "^4.5.8", 53 | "@mui/icons-material": "^5.11.0", 54 | "@mui/material": "^5.11.7", 55 | "@mui/system": "^5.11.7", 56 | "notistack": "^2.0.8", 57 | "react": "18.2.0", 58 | "react-dom": "18.2.0", 59 | "react-router-dom": "^6.8.0" 60 | } 61 | } -------------------------------------------------------------------------------- /project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "todo-list", 3 | "$schema": "node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "./src", 5 | "projectType": "application", 6 | "targets": { 7 | "build": { 8 | "executor": "@nrwl/vite:build", 9 | "outputs": [ 10 | "{options.outputPath}" 11 | ], 12 | "defaultConfiguration": "production", 13 | "options": { 14 | "outputPath": "dist/todo-list" 15 | }, 16 | "configurations": { 17 | "development": { 18 | "mode": "development" 19 | }, 20 | "production": { 21 | "mode": "production" 22 | } 23 | } 24 | }, 25 | "serve": { 26 | "executor": "@nrwl/vite:dev-server", 27 | "defaultConfiguration": "development", 28 | "options": { 29 | "buildTarget": "todo-list:build" 30 | }, 31 | "configurations": { 32 | "development": { 33 | "buildTarget": "todo-list:build:development", 34 | "hmr": true, 35 | "proxyConfig": "proxy.conf.json" 36 | }, 37 | "production": { 38 | "buildTarget": "todo-list:build:production", 39 | "hmr": false 40 | } 41 | } 42 | }, 43 | "preview": { 44 | "executor": "@nrwl/vite:preview-server", 45 | "defaultConfiguration": "development", 46 | "options": { 47 | "buildTarget": "todo-list:build" 48 | }, 49 | "configurations": { 50 | "development": { 51 | "buildTarget": "todo-list:build:development" 52 | }, 53 | "production": { 54 | "buildTarget": "todo-list:build:production" 55 | } 56 | } 57 | }, 58 | "test": { 59 | "executor": "@nrwl/vite:test", 60 | "outputs": [ 61 | "coverage/todo-list" 62 | ], 63 | "options": { 64 | "passWithNoTests": true, 65 | "reportsDirectory": "coverage/todo-list" 66 | } 67 | }, 68 | "lint": { 69 | "executor": "@nrwl/linter:eslint", 70 | "outputs": [ 71 | "{options.outputFile}" 72 | ], 73 | "options": { 74 | "lintFilePatterns": [ 75 | "./src/**/*.{ts,tsx,js,jsx}" 76 | ] 77 | } 78 | } 79 | }, 80 | "tags": [] 81 | } -------------------------------------------------------------------------------- /src/app/pages/Todos/List/index.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Grid, Paper, Tab, Tabs } from "@mui/material"; 2 | import { useSnackbar } from "notistack"; 3 | import { useState } from "react"; 4 | import { Todo } from "../../../models"; 5 | import AddTodoForm from "./components/AddTodoForm"; 6 | import List from "./components/List"; 7 | 8 | const styles = { 9 | Paper: { 10 | padding: 20, 11 | margin: "auto", 12 | textAlign: "center", 13 | width: 500 14 | } 15 | } as const; 16 | 17 | function ListPage() { 18 | const auth = true; 19 | const { enqueueSnackbar } = useSnackbar(); 20 | const [currentTab, setCurrentTab] = useState(0); 21 | const [list, setList] = useState([{ 22 | id: 1, 23 | text: 'Todo Example 1', 24 | }, { 25 | id: 2, 26 | text: 'Todo Example 2', 27 | }, { 28 | id: 3, 29 | text: 'Todo Example 3', 30 | assigneeId: 1 31 | }]); 32 | 33 | const addToList = (text: string) => { 34 | enqueueSnackbar('Add new Todo', { 35 | variant: 'success' 36 | }) 37 | }; 38 | 39 | 40 | return ( 41 | <> 42 | {auth && 43 | 52 | { 58 | setCurrentTab(newValue) 59 | }} 60 | aria-label="basic tabs example"> 61 | 62 | 63 | 64 | 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 77 | 78 | 79 | ); 80 | 81 | } 82 | 83 | export default ListPage; 84 | -------------------------------------------------------------------------------- /src/app/components/AppBar/index.tsx: -------------------------------------------------------------------------------- 1 | import { AccountCircle } from '@mui/icons-material'; 2 | import { IconButton, Menu, MenuItem, Toolbar, Typography } from '@mui/material'; 3 | import MuiAppBar from '@mui/material/AppBar'; 4 | import { useState } from 'react'; 5 | import { Link } from 'react-router-dom'; 6 | 7 | interface AppBarProps { 8 | auth: boolean, 9 | logout: () => void 10 | } 11 | 12 | export default function ({ auth, logout }: AppBarProps) { 13 | const [anchorEl, setAnchorEl] = useState(null); 14 | 15 | const handleMenu = (event: React.MouseEvent) => { 16 | setAnchorEl(event.currentTarget); 17 | }; 18 | 19 | const handleClose = () => { 20 | setAnchorEl(null); 21 | }; 22 | 23 | const handleLogout = () => { 24 | logout() 25 | }; 26 | 27 | return 31 | 32 | 36 | 37 | Todo List 38 | 39 | 40 |
41 | {auth && ( 42 | <> 43 | 51 | 52 | 53 | 68 | Logout 69 | 70 | 71 | )} 72 | {!auth && 73 | 80 | 81 | 82 | } 83 |
84 |
85 |
86 | } -------------------------------------------------------------------------------- /src/app/pages/Todos/List/components/TodoItem/index.tsx: -------------------------------------------------------------------------------- 1 | import { AssignmentInd, Build, Delete } from "@mui/icons-material"; 2 | import { Grid, Paper } from "@mui/material"; 3 | import IconButton from "@mui/material/IconButton"; 4 | import { useCallback, useRef, useState } from "react"; 5 | import { Link } from "react-router-dom"; 6 | import { Todo } from "../../../../../models/todo"; 7 | 8 | const styles = { 9 | Icon: { 10 | marginLeft: "auto" 11 | }, 12 | Paper: { 13 | margin: "auto", 14 | padding: 10, 15 | display: "flex", 16 | alignItems: "center", 17 | marginTop: 10, 18 | width: 500 19 | } 20 | } as const; 21 | 22 | interface TodoItemProp { 23 | auth: boolean; 24 | todo: Todo 25 | deleteTodo: () => void; 26 | assignToMe: () => void; 27 | } 28 | 29 | function TodoItem({ 30 | auth, 31 | todo, 32 | deleteTodo, 33 | assignToMe 34 | }: TodoItemProp) { 35 | const [fade, setFade] = useState(false) 36 | const gridRef = useRef(null) 37 | 38 | const onDeleteBtnClick: React.MouseEventHandler = useCallback(() => { 39 | deleteTodo() 40 | }, [deleteTodo]); 41 | 42 | const onAssignBtnClick: React.MouseEventHandler = useCallback(() => { 43 | assignToMe() 44 | }, [assignToMe]); 45 | 46 | // const onDeleteBtnClick: React.MouseEventHandler = useCallback(() => { 47 | // const fade = true; 48 | // setFade(fade); 49 | 50 | // new Promise(function (resolve, reject) { 51 | // setTimeout(function () { 52 | // resolve(true); 53 | // }, 500); 54 | // }) 55 | // .then(() => deleteTodo()); 56 | // }, [deleteTodo]); 57 | 58 | const gridClass = fade ? "fade-out" : ""; 59 | const canAssign = auth && !todo.assigneeId; 60 | 61 | return ( 62 | 68 | 69 | {todo.text} 70 | { 71 | canAssign && 77 | 78 | 79 | } 80 | 84 | 88 | 89 | 90 | 91 | 96 | 97 | 98 | 99 | 100 | ); 101 | } 102 | 103 | export default TodoItem; 104 | --------------------------------------------------------------------------------