├── 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 |
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 |
--------------------------------------------------------------------------------