From 62617bfb6f059b1e3437033eaf5a2f8c912d1ec0 Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 22 Feb 2025 15:09:28 +0300 Subject: [PATCH 1/5] added spa --- cool_todo_manager/package-lock.json | 43 +++++++++++++++ cool_todo_manager/package.json | 2 + cool_todo_manager/src/App.tsx | 15 ++++-- cool_todo_manager/src/api/axios.ts | 14 +++++ cool_todo_manager/src/api/queryClient.ts | 11 ++++ .../src/components/CardGroup/CardGroup.tsx | 39 +++++++------- cool_todo_manager/src/index.css | 4 ++ cool_todo_manager/src/main.tsx | 8 +-- .../src/pages/auth/LoginPage/LoginPage.tsx | 52 +++++++++++++++++++ .../pages/auth/RegisterPage/RegisterPage.tsx | 49 +++++++++++++++++ cool_todo_manager/src/pages/main/MainPage.tsx | 10 ++++ cool_todo_manager/src/routes/routes.tsx | 18 +++++++ 12 files changed, 236 insertions(+), 29 deletions(-) create mode 100644 cool_todo_manager/src/api/axios.ts create mode 100644 cool_todo_manager/src/api/queryClient.ts create mode 100644 cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx create mode 100644 cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx create mode 100644 cool_todo_manager/src/pages/main/MainPage.tsx create mode 100644 cool_todo_manager/src/routes/routes.tsx diff --git a/cool_todo_manager/package-lock.json b/cool_todo_manager/package-lock.json index 28cdaa3..6d7f662 100644 --- a/cool_todo_manager/package-lock.json +++ b/cool_todo_manager/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "@hello-pangea/dnd": "^18.0.1", + "@radix-ui/react-form": "^0.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.0", "@tailwindcss/vite": "^4.0.6", @@ -18,6 +19,7 @@ "jotai": "^2.12.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-router-dom": "^6.26.2", "tailwindcss": "^4.0.6" }, "devDependencies": { @@ -2545,6 +2547,15 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", + "integrity": "sha512-baiMx18+IMuD1yyvOGaHM9QrVUPGGG0jC+z+IPHnRJWUAUvaKuWKyE8gjDj2rzv3sz9zOGoRSPgeBVHRhZnBlA==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.34.7", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.34.7.tgz", @@ -5323,6 +5334,38 @@ } } }, + "node_modules/react-router": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.26.2.tgz", + "integrity": "sha512-tvN1iuT03kHgOFnLPfLJ8V95eijteveqdOSk+srqfePtQvqCExB8eHOYnlilbOcyJyKnYkr1vJvf7YqotAJu1A==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.19.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.26.2.tgz", + "integrity": "sha512-z7YkaEW0Dy35T3/QKPYB1LjMK2R1fxnHO8kWpUMTBdfVzZrWOiY9a7CtN8HqdWtDUWd5FY6Dl8HFsqVwH4uOtQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.19.2", + "react-router": "6.26.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-style-singleton": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", diff --git a/cool_todo_manager/package.json b/cool_todo_manager/package.json index 091c7b8..e51fa4e 100644 --- a/cool_todo_manager/package.json +++ b/cool_todo_manager/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@hello-pangea/dnd": "^18.0.1", + "@radix-ui/react-form": "^0.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.0", "@tailwindcss/vite": "^4.0.6", @@ -20,6 +21,7 @@ "jotai": "^2.12.0", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-router-dom": "^6.26.2", "tailwindcss": "^4.0.6" }, "devDependencies": { diff --git a/cool_todo_manager/src/App.tsx b/cool_todo_manager/src/App.tsx index 1cf69e1..3d02b76 100644 --- a/cool_todo_manager/src/App.tsx +++ b/cool_todo_manager/src/App.tsx @@ -1,14 +1,21 @@ import { Theme } from '@radix-ui/themes'; import '@radix-ui/themes/styles.css'; +import { QueryClientProvider } from '@tanstack/react-query'; +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import queryClient from './api/queryClient'; import './App.css'; -import MainBoard from './components/MainBoard/MainBoard'; +import MyRoutes from './routes/routes'; + +const router = createBrowserRouter(MyRoutes); function App() { return ( - - + + {/* */} - + + + ); } diff --git a/cool_todo_manager/src/api/axios.ts b/cool_todo_manager/src/api/axios.ts new file mode 100644 index 0000000..7b0f02d --- /dev/null +++ b/cool_todo_manager/src/api/axios.ts @@ -0,0 +1,14 @@ +import axios from "axios"; + +const BASE_URL = 'http://localhost:4567'; + +export const axiosBase = axios.create({ + baseURL: BASE_URL, +}); + +export const axiosAuth = axios.create({ + baseURL: BASE_URL, + headers: { + Authorization: `Bearer ${localStorage.getItem('token')}` // Maybe we will use cookies + } +}); \ No newline at end of file diff --git a/cool_todo_manager/src/api/queryClient.ts b/cool_todo_manager/src/api/queryClient.ts new file mode 100644 index 0000000..75e4ae8 --- /dev/null +++ b/cool_todo_manager/src/api/queryClient.ts @@ -0,0 +1,11 @@ +import { QueryClient } from "@tanstack/react-query"; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: 2, + }, + }, +}) + +export default queryClient; \ No newline at end of file diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index daa0ea9..cdd8109 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -8,29 +8,26 @@ const tasks = [ ]; type TCardGroup = { - id: string -} + id: string; +}; export default function CardGroup(props: TCardGroup) { return ( - - {(provided) => ( - - {tasks.map((task, i) => ( - - ))} - {provided.placeholder} - - )} - + + {(provided) => ( + + {tasks.map((task, i) => ( + + ))} + {provided.placeholder} + + )} + ); } diff --git a/cool_todo_manager/src/index.css b/cool_todo_manager/src/index.css index d4b5078..358e675 100644 --- a/cool_todo_manager/src/index.css +++ b/cool_todo_manager/src/index.css @@ -1 +1,5 @@ @import 'tailwindcss'; + +#root { + height: 100vh; +} \ No newline at end of file diff --git a/cool_todo_manager/src/main.tsx b/cool_todo_manager/src/main.tsx index 68176a7..35f6c8f 100644 --- a/cool_todo_manager/src/main.tsx +++ b/cool_todo_manager/src/main.tsx @@ -1,5 +1,5 @@ -import { createRoot } from "react-dom/client"; -import App from "./App.tsx"; -import "./index.css"; +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; -createRoot(document.getElementById("root")!).render(); +createRoot(document.getElementById('root')!).render(); diff --git a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx new file mode 100644 index 0000000..61064a3 --- /dev/null +++ b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx @@ -0,0 +1,52 @@ +import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes'; +import { Form } from 'radix-ui'; + +export default function LoginPage() { + return ( + <> + + Login + + e.preventDefault()}> + + + Email is required + + + + Email is not valid + + + + + + + + + + + + Password is required + + + + + + + + + + + + + + ); +} diff --git a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx new file mode 100644 index 0000000..7dceaf2 --- /dev/null +++ b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx @@ -0,0 +1,49 @@ +import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes'; +import { Form } from 'radix-ui'; + +export default function RegisterPage() { + return ( + + Register + e.preventDefault()}> + + + Email is required + + + + Email is not valid + + + + + + + + + + + + Password is required + + + + + + + + + + + + + ); +} diff --git a/cool_todo_manager/src/pages/main/MainPage.tsx b/cool_todo_manager/src/pages/main/MainPage.tsx new file mode 100644 index 0000000..e03c3e5 --- /dev/null +++ b/cool_todo_manager/src/pages/main/MainPage.tsx @@ -0,0 +1,10 @@ +import { Box } from '@radix-ui/themes' +import { Outlet } from 'react-router-dom' + +export default function MainPage() { + return ( + + + + ) +} diff --git a/cool_todo_manager/src/routes/routes.tsx b/cool_todo_manager/src/routes/routes.tsx new file mode 100644 index 0000000..2801816 --- /dev/null +++ b/cool_todo_manager/src/routes/routes.tsx @@ -0,0 +1,18 @@ +import { createRoutesFromElements, Route } from 'react-router-dom'; +import MainBoard from '../components/MainBoard/MainBoard'; +import LoginPage from '../pages/auth/LoginPage/LoginPage'; +import RegisterPage from '../pages/auth/RegisterPage/RegisterPage'; +import MainPage from '../pages/main/MainPage'; + +const MyRoutes = createRoutesFromElements( + <> + }> + } /> + + + } /> + } /> + , +); + +export default MyRoutes; From e82b06b508bdd059d580626a32fd5c98621ba34f Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 1 Mar 2025 21:32:57 +0300 Subject: [PATCH 2/5] redux setup --- cool_todo_manager/package-lock.json | 69 +++++++++ cool_todo_manager/package.json | 4 + cool_todo_manager/src/App.tsx | 10 +- cool_todo_manager/src/api/RTKQuery.ts | 16 ++ .../components/AuthWrapper/AuthWrapper.tsx | 15 ++ .../src/components/CardGroup/CardGroup.tsx | 5 +- .../src/components/MainBoard/MainBoard.tsx | 35 ++++- .../src/pages/auth/LoginPage/LoginPage.tsx | 42 +++-- cool_todo_manager/src/services/mainApi.ts | 145 ++++++++++++++++++ 9 files changed, 320 insertions(+), 21 deletions(-) create mode 100644 cool_todo_manager/src/api/RTKQuery.ts create mode 100644 cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx create mode 100644 cool_todo_manager/src/services/mainApi.ts diff --git a/cool_todo_manager/package-lock.json b/cool_todo_manager/package-lock.json index 6d7f662..b47a309 100644 --- a/cool_todo_manager/package-lock.json +++ b/cool_todo_manager/package-lock.json @@ -12,18 +12,22 @@ "@radix-ui/react-form": "^0.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.0", + "@reduxjs/toolkit": "^2.6.0", "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "axios": "^1.7.9", "jotai": "^2.12.0", + "js-cookie": "^3.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-redux": "^9.2.0", "react-router-dom": "^6.26.2", "tailwindcss": "^4.0.6" }, "devDependencies": { "@eslint/js": "^9.19.0", + "@types/js-cookie": "^3.0.6", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", @@ -2547,6 +2551,30 @@ } } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.6.0.tgz", + "integrity": "sha512-mWJCYpewLRyTuuzRSEC/IwIBBkYg2dKtQas8mty5MaV2iXzcmicS3gW554FDeOvLnY3x13NIk8MB1e8wHO7rqQ==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.2.tgz", @@ -3131,6 +3159,13 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "license": "MIT" }, + "node_modules/@types/js-cookie": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-3.0.6.tgz", + "integrity": "sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -4423,6 +4458,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -4520,6 +4565,15 @@ } } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -5394,12 +5448,27 @@ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", "license": "MIT" }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerator-runtime": { "version": "0.14.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", "license": "MIT" }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", diff --git a/cool_todo_manager/package.json b/cool_todo_manager/package.json index e51fa4e..c9a7b8f 100644 --- a/cool_todo_manager/package.json +++ b/cool_todo_manager/package.json @@ -14,18 +14,22 @@ "@radix-ui/react-form": "^0.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/themes": "^3.2.0", + "@reduxjs/toolkit": "^2.6.0", "@tailwindcss/vite": "^4.0.6", "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "axios": "^1.7.9", "jotai": "^2.12.0", + "js-cookie": "^3.0.5", "react": "^19.0.0", "react-dom": "^19.0.0", + "react-redux": "^9.2.0", "react-router-dom": "^6.26.2", "tailwindcss": "^4.0.6" }, "devDependencies": { "@eslint/js": "^9.19.0", + "@types/js-cookie": "^3.0.6", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", diff --git a/cool_todo_manager/src/App.tsx b/cool_todo_manager/src/App.tsx index 3d02b76..0116187 100644 --- a/cool_todo_manager/src/App.tsx +++ b/cool_todo_manager/src/App.tsx @@ -1,8 +1,10 @@ import { Theme } from '@radix-ui/themes'; import '@radix-ui/themes/styles.css'; import { QueryClientProvider } from '@tanstack/react-query'; +import { Provider } from 'react-redux'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import queryClient from './api/queryClient'; +import { store } from './api/RTKQuery'; import './App.css'; import MyRoutes from './routes/routes'; @@ -10,10 +12,12 @@ const router = createBrowserRouter(MyRoutes); function App() { return ( - + - {/* */} - + + {/* */} + + ); diff --git a/cool_todo_manager/src/api/RTKQuery.ts b/cool_todo_manager/src/api/RTKQuery.ts new file mode 100644 index 0000000..7091df8 --- /dev/null +++ b/cool_todo_manager/src/api/RTKQuery.ts @@ -0,0 +1,16 @@ +import { configureStore } from '@reduxjs/toolkit'; +import { setupListeners } from '@reduxjs/toolkit/query'; +import { authApi, mainApi } from '../services/mainApi'; + +export const store = configureStore({ + reducer: { + [mainApi.reducerPath]: mainApi.reducer, + [authApi.reducerPath]: authApi.reducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware() + .concat(mainApi.middleware) + .concat(authApi.middleware), +}); + +setupListeners(store.dispatch); diff --git a/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx new file mode 100644 index 0000000..ebafd16 --- /dev/null +++ b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx @@ -0,0 +1,15 @@ +import { useNavigate } from "react-router-dom" + +export default function AuthWrapper() { + const navigate = useNavigate() + + if(!localStorage.getItem('token')) { + navigate('/login') + } + + return ( +
+ +
+ ) +} diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index cdd8109..f41d337 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -1,5 +1,6 @@ import { Droppable } from '@hello-pangea/dnd'; import { Box } from '@radix-ui/themes'; +import { useState } from 'react'; import TaskCard from '../TaskCard/TaskCard'; const tasks = [ @@ -12,11 +13,13 @@ type TCardGroup = { }; export default function CardGroup(props: TCardGroup) { + const [localTasks, setLocalTasks] = useState(tasks) + return ( {(provided) => ( - {tasks.map((task, i) => ( + {localTasks.map((task, i) => ( { + result; + }; - const dragEndHandle = (result: TDragResult ) => { - result - } + const { data } = useGetTasksQuery(); + + const [create] = useCreateTaskMutation(); + + const onClick = () => { + create({ + title: 'My Task 123', + projectId: 1, + assignedUserId: 2, + deadline: '2025-03-01T12:00:00Z', + }); + }; return ( - - - - + <> + + + + + + ); } diff --git a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx index 61064a3..ebb8fa0 100644 --- a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx +++ b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx @@ -1,24 +1,42 @@ import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes'; import { Form } from 'radix-ui'; +import { useState } from 'react'; +import { useLoginMutation } from '../../../services/mainApi'; export default function LoginPage() { + const [login, { isError }] = useLoginMutation(); + + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + + const submitHandler = (e: React.FormEvent) => { + e.preventDefault(); + login({ username, password }); + }; + return ( <> - Login + + Login + - e.preventDefault()}> + - Email is required - - - - Email is not valid + Username is required - + setUsername(e.target.value)} + > @@ -33,16 +51,22 @@ export default function LoginPage() { setPassword(e.target.value)} > + {isError && ( + {'Unable to login. Please try again.'} + )} + diff --git a/cool_todo_manager/src/services/mainApi.ts b/cool_todo_manager/src/services/mainApi.ts new file mode 100644 index 0000000..cf5f1fe --- /dev/null +++ b/cool_todo_manager/src/services/mainApi.ts @@ -0,0 +1,145 @@ +import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; + +export const mainApi = createApi({ + reducerPath: 'api', + baseQuery: fetchBaseQuery({ + baseUrl: 'http://109.107.166.17:3000/api/', + prepareHeaders: (headers) => { + headers.set( + 'Authorization', + `Bearer ${localStorage.getItem('token')}`, + ); + return headers; + }, + }), + tagTypes: ['Task', 'Project'], + endpoints: (builder) => ({ + getTasks: builder.query({ + query: () => ({ + url: 'tasks', + method: 'GET', + }), + providesTags: (result) => + result + ? [ + ...result.map((task) => ({ + type: 'Task' as const, + id: task, + })), + { type: 'Task', id: 'LIST' }, + ] + : [{ type: 'Task', id: 'LIST' }], + }), + getTask: builder.query({ + query: (id) => ({ + url: `tasks/${id}`, + method: 'GET', + }), + providesTags: (result, error, id) => [{ type: 'Task', id }], + }), + updateTask: builder.mutation({ + query: (task) => ({ + url: `tasks/${task.id}`, + method: 'PATCH', + body: task, + }), + invalidatesTags: (result, error, id) => [{ type: 'Task', id }], + }), + deleteTask: builder.mutation({ + query: (id) => ({ + url: `tasks/${id}`, + method: 'DELETE', + }), + invalidatesTags: (result, error, id) => [{ type: 'Task', id }], + }), + createTask: builder.mutation({ + query: (newTask) => ({ + url: 'tasks/create', + method: 'POST', + body: newTask, + }), + invalidatesTags: [{ type: 'Task', id: 'LIST' }], + }), + + // PROJECTS + createProject: builder.mutation({ + query: (newProject) => ({ + url: 'tasks/create', + method: 'POST', + body: newProject, + }), + invalidatesTags: [{ type: 'Project', id: 'LIST' }], + }), + getProjects: builder.query({ + query: () => ({ + url: 'projects', + method: 'GET', + }), + providesTags: (result, error, id) => [ + { type: 'Project', id: 'LIST' }, + ], + }), + getProject: builder.query({ + query: (id) => ({ + url: `projects/${id}`, + method: 'GET', + }), + providesTags: (result, error, id) => [{ type: 'Project', id }], + }), + updateProject: builder.mutation({ + query: ({ id, project }) => ({ + url: `projects/${id}`, + method: 'PATCH', + body: project, + }), + invalidatesTags: (result, error, id) => [{ type: 'Project', id }], + }), + deleteProject: builder.mutation({ + query: (id) => ({ + url: `projects/${id}`, + method: 'DELETE', + }), + invalidatesTags: (result, error, id) => [{ type: 'Project', id }], + }), + }), +}); + +export const authApi = createApi({ + reducerPath: 'authApi', + baseQuery: fetchBaseQuery({ + baseUrl: 'http://109.107.166.17:3000/api/auth/', + + }), + endpoints: (builder) => ({ + login: builder.mutation({ + query: (credentials) => ({ + url: 'login', + method: 'POST', + body: credentials, + }), + async onQueryStarted(arg, { queryFulfilled }) { + try { + const response = await queryFulfilled; + if (response.data) { + localStorage.setItem( + 'token', + response.data.access_token, + ); + window.location.href = '/'; + } + } catch (error) {} + }, + }), + register: builder.mutation({ + query: (credentials) => ({ + url: 'register', + method: 'POST', + body: credentials, + }), + }), + }), +}); + +export const { useLoginMutation, useRegisterMutation } = authApi; +export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation } = mainApi; +export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi; From cabb8a9b27f96cee7552935595a01c776b54962f Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 2 Mar 2025 16:06:28 +0300 Subject: [PATCH 3/5] redux setup --- cool_todo_manager/src/api/RTKQuery.ts | 19 ++++- .../components/AuthWrapper/AuthWrapper.tsx | 19 ++--- .../src/components/CardGroup/CardGroup.tsx | 75 ++++++++++++++----- .../CreateTaskDialog/CreateTaskDialog.tsx | 63 ++++++++++++++++ .../DeleteProjectDialog/CreateTaskDialog.tsx | 36 +++++++++ .../src/components/MainBoard/MainBoard.tsx | 33 +++++--- .../src/components/TaskCard/TaskCard.tsx | 12 +-- .../src/pages/auth/LoginPage/LoginPage.tsx | 18 ++++- .../pages/auth/RegisterPage/RegisterPage.tsx | 29 ++++++- cool_todo_manager/src/routes/routes.tsx | 10 ++- cool_todo_manager/src/services/mainApi.ts | 20 ++++- 11 files changed, 274 insertions(+), 60 deletions(-) create mode 100644 cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx create mode 100644 cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx diff --git a/cool_todo_manager/src/api/RTKQuery.ts b/cool_todo_manager/src/api/RTKQuery.ts index 7091df8..cd93886 100644 --- a/cool_todo_manager/src/api/RTKQuery.ts +++ b/cool_todo_manager/src/api/RTKQuery.ts @@ -1,7 +1,22 @@ -import { configureStore } from '@reduxjs/toolkit'; +import { configureStore, isRejectedWithValue, Middleware } from '@reduxjs/toolkit'; import { setupListeners } from '@reduxjs/toolkit/query'; import { authApi, mainApi } from '../services/mainApi'; +const loggerMiddleware: Middleware = (_store) => (next) => (action) => { + console.log('dispatching', action); + if(isRejectedWithValue(action)) { + // @ts-ignore + const statusCode = action.payload.status; + if(statusCode === 401) { + console.log('Unauthorized, redirecting to login page'); + localStorage.removeItem('token'); + window.location.href = '/login'; + } + } + let result = next(action); + return result; +}; + export const store = configureStore({ reducer: { [mainApi.reducerPath]: mainApi.reducer, @@ -9,8 +24,10 @@ export const store = configureStore({ }, middleware: (getDefaultMiddleware) => getDefaultMiddleware() + .prepend(loggerMiddleware) .concat(mainApi.middleware) .concat(authApi.middleware), }); setupListeners(store.dispatch); + diff --git a/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx index ebafd16..07d3bac 100644 --- a/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx +++ b/cool_todo_manager/src/components/AuthWrapper/AuthWrapper.tsx @@ -1,15 +1,12 @@ -import { useNavigate } from "react-router-dom" +import { PropsWithChildren } from 'react'; +import { Navigate } from 'react-router-dom'; -export default function AuthWrapper() { - const navigate = useNavigate() +export default function AuthWrapper(props: PropsWithChildren) { - if(!localStorage.getItem('token')) { - navigate('/login') - } + if (!localStorage.getItem('token')) { + console.log('No token found, redirecting to login'); + return + } - return ( -
- -
- ) + return
{props.children}
; } diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index f41d337..ce655b8 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -1,34 +1,69 @@ import { Droppable } from '@hello-pangea/dnd'; -import { Box } from '@radix-ui/themes'; -import { useState } from 'react'; +import { Box, Button, Flex, Text } from '@radix-ui/themes'; +import { + useCreateTaskMutation, + useDeleteProjectMutation, + useGetTasksForGroupQuery, +} from '../../services/mainApi'; import TaskCard from '../TaskCard/TaskCard'; - -const tasks = [ - { id: 1, title: 'Task 1', description: 'Description for Task 1' }, - { id: 2, title: 'Task 2', description: 'Description for Task 2' }, -]; +import CreateTaskDialog from './CreateTaskDialog/CreateTaskDialog'; +import DeleteProjectDialog from './DeleteProjectDialog/CreateTaskDialog'; type TCardGroup = { id: string; + title: string; }; export default function CardGroup(props: TCardGroup) { - const [localTasks, setLocalTasks] = useState(tasks) + const { data, isLoading } = useGetTasksForGroupQuery(props.id); + + const [createTaskForGroup] = useCreateTaskMutation(); + const [deleteProject] = useDeleteProjectMutation(); + + const createTask = (taskText: string, date: string) => { + createTaskForGroup({ + title: taskText, + projectId: props.id, + assignedUserId: 1, + deadline: date, + }); + }; + + const deleteGroup = () => { + deleteProject(props.id); + }; return ( - + {(provided) => ( - - {localTasks.map((task, i) => ( - - ))} - {provided.placeholder} + + {props.title} + + {data && + data.map((task, i) => ( + + ))} + + + + + + + + + )} diff --git a/cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx b/cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx new file mode 100644 index 0000000..e9fa374 --- /dev/null +++ b/cool_todo_manager/src/components/CardGroup/CreateTaskDialog/CreateTaskDialog.tsx @@ -0,0 +1,63 @@ +import { Button, Dialog, Flex, Text, TextField } from '@radix-ui/themes'; +import { PropsWithChildren, useState } from 'react'; + +type TCreateTaskDialog = { + onClose: (text: string, timeString: string) => void; +} & PropsWithChildren; + +export default function CreateTaskDialog(props: TCreateTaskDialog) { + const [text, setText] = useState(''); + const [deadline, setDeadline] = useState(''); + + return ( + + {props.children} + + + Task creation + + + + + + + + + + + + + + + + + ); +} diff --git a/cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx b/cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx new file mode 100644 index 0000000..7047c5c --- /dev/null +++ b/cool_todo_manager/src/components/CardGroup/DeleteProjectDialog/CreateTaskDialog.tsx @@ -0,0 +1,36 @@ +import { Button, Dialog, Flex } from '@radix-ui/themes'; +import { PropsWithChildren } from 'react'; + +type TCreateTaskDialog = { + onClose: () => void; +} & PropsWithChildren; + +export default function DeleteProjectDialog(props: TCreateTaskDialog) { + return ( + + {props.children} + + + Delete project? + + + + + + + + + + + + ); +} diff --git a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx index a3573b7..31c9c93 100644 --- a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx +++ b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx @@ -1,8 +1,9 @@ import { DragDropContext } from '@hello-pangea/dnd'; -import { Button } from '@radix-ui/themes'; +import { Button, Flex, ScrollArea } from '@radix-ui/themes'; import { + useCreateProjectMutation, useCreateTaskMutation, - useGetTasksQuery, + useGetProjectsQuery, } from '../../services/mainApi'; import CardGroup from '../CardGroup/CardGroup'; @@ -11,26 +12,40 @@ export default function MainBoard() { result; }; - const { data } = useGetTasksQuery(); - const [create] = useCreateTaskMutation(); + const [createProject] = useCreateProjectMutation(); + const { data: cringe, isLoading } = useGetProjectsQuery({}); const onClick = () => { create({ title: 'My Task 123', - projectId: 1, - assignedUserId: 2, + projectId: 4, + assignedUserId: 1, deadline: '2025-03-01T12:00:00Z', }); }; + const onClick1 = () => { + createProject({ + title: 'My project test 12', + description: 'Test desc 123 123 123', + }); + }; + return ( <> - - + + + {!isLoading && + (cringe as any[]).map((item: any) => ( + + ))} + + - + + ); } diff --git a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx index 98f44a9..490a6fe 100644 --- a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx +++ b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx @@ -1,12 +1,12 @@ import { Draggable } from '@hello-pangea/dnd'; -import { DragHandleHorizontalIcon } from '@radix-ui/react-icons'; -import { Box, Button, Card, Flex, Text } from '@radix-ui/themes'; +import { Button, Card, Flex, Text } from '@radix-ui/themes'; type TTaskCard = { title?: string; description?: string; id?: string; index?: number; + status: "todo" | "in-progress" | "completed"; }; export default function TaskCard(props: TTaskCard) { @@ -21,14 +21,6 @@ export default function TaskCard(props: TTaskCard) { {props.title} - - - - -
)} diff --git a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx index ebb8fa0..a5a6bc5 100644 --- a/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx +++ b/cool_todo_manager/src/pages/auth/LoginPage/LoginPage.tsx @@ -1,4 +1,12 @@ -import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes'; +import { + Box, + Button, + Card, + Heading, + Link, + Text, + TextField, +} from '@radix-ui/themes'; import { Form } from 'radix-ui'; import { useState } from 'react'; import { useLoginMutation } from '../../../services/mainApi'; @@ -66,9 +74,15 @@ export default function LoginPage() { + + + + Register + + diff --git a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx index 7dceaf2..21168d0 100644 --- a/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx +++ b/cool_todo_manager/src/pages/auth/RegisterPage/RegisterPage.tsx @@ -1,11 +1,24 @@ -import { Button, Card, Heading, Text, TextField } from '@radix-ui/themes'; +import { + Box, + Button, + Card, + Heading, + Link, + Text, + TextField, +} from '@radix-ui/themes'; import { Form } from 'radix-ui'; export default function RegisterPage() { return ( - Register - e.preventDefault()}> + + Register + + e.preventDefault()} + > Email is required @@ -42,7 +55,15 @@ export default function RegisterPage() { - + + + + + Login + + ); diff --git a/cool_todo_manager/src/routes/routes.tsx b/cool_todo_manager/src/routes/routes.tsx index 2801816..3b10cf0 100644 --- a/cool_todo_manager/src/routes/routes.tsx +++ b/cool_todo_manager/src/routes/routes.tsx @@ -1,4 +1,5 @@ import { createRoutesFromElements, Route } from 'react-router-dom'; +import AuthWrapper from '../components/AuthWrapper/AuthWrapper'; import MainBoard from '../components/MainBoard/MainBoard'; import LoginPage from '../pages/auth/LoginPage/LoginPage'; import RegisterPage from '../pages/auth/RegisterPage/RegisterPage'; @@ -6,7 +7,14 @@ import MainPage from '../pages/main/MainPage'; const MyRoutes = createRoutesFromElements( <> - }> + + + + } + > } /> diff --git a/cool_todo_manager/src/services/mainApi.ts b/cool_todo_manager/src/services/mainApi.ts index cf5f1fe..f39a73d 100644 --- a/cool_todo_manager/src/services/mainApi.ts +++ b/cool_todo_manager/src/services/mainApi.ts @@ -30,6 +30,22 @@ export const mainApi = createApi({ ] : [{ type: 'Task', id: 'LIST' }], }), + getTasksForGroup: builder.query({ + query: (id: string) => ({ + url: `tasks/project/${id}`, + method: 'GET', + }), + providesTags: (result) => + result + ? [ + ...result.map((task) => ({ + type: 'Task' as const, + id: task, + })), + { type: 'Task', id: 'LIST' }, + ] + : [{ type: 'Task', id: 'LIST' }], + }), getTask: builder.query({ query: (id) => ({ url: `tasks/${id}`, @@ -64,7 +80,7 @@ export const mainApi = createApi({ // PROJECTS createProject: builder.mutation({ query: (newProject) => ({ - url: 'tasks/create', + url: 'projects/create', method: 'POST', body: newProject, }), @@ -141,5 +157,5 @@ export const authApi = createApi({ }); export const { useLoginMutation, useRegisterMutation } = authApi; -export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation } = mainApi; +export const { useGetTasksQuery, useCreateTaskMutation, useUpdateTaskMutation, useDeleteTaskMutation, useGetTasksForGroupQuery } = mainApi; export const { useGetProjectsQuery, useCreateProjectMutation, useUpdateProjectMutation, useDeleteProjectMutation } = mainApi; From f857ae3e2db6d3d562957c4cf1d7cfd3e2dc4f3f Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 2 Mar 2025 17:45:56 +0300 Subject: [PATCH 4/5] rainbow vomit --- .../src/components/CardGroup/CardGroup.tsx | 2 +- .../src/components/MainBoard/MainBoard.tsx | 3 +- .../src/components/TaskCard/TaskCard.tsx | 49 +++++++++++++++++-- cool_todo_manager/src/services/mainApi.ts | 2 +- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index ce655b8..fa0ed1d 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -46,7 +46,7 @@ export default function CardGroup(props: TCardGroup) { data.map((task, i) => ( - + {!isLoading && (cringe as any[]).map((item: any) => ( @@ -44,7 +44,6 @@ export default function MainBoard() { - ); diff --git a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx index 490a6fe..83a99dd 100644 --- a/cool_todo_manager/src/components/TaskCard/TaskCard.tsx +++ b/cool_todo_manager/src/components/TaskCard/TaskCard.tsx @@ -1,26 +1,65 @@ import { Draggable } from '@hello-pangea/dnd'; -import { Button, Card, Flex, Text } from '@radix-ui/themes'; +import { Badge, Button, Card, Flex, Text } from '@radix-ui/themes'; +import { useUpdateTaskMutation } from '../../services/mainApi'; type TTaskCard = { title?: string; description?: string; id?: string; index?: number; - status: "todo" | "in-progress" | "completed"; + status: 'todo' | 'in-progress' | 'completed'; }; +const badgeNames = { + todo: 'To do', + 'in-progress': 'In progress', + completed: 'Completed', +} as const + +const badgeColors = { + todo: 'blue', + 'in-progress': 'orange', + completed: 'green', +} as const + export default function TaskCard(props: TTaskCard) { + const [updateTask] = useUpdateTaskMutation(); + + const updateStatus = (newStatus: 'todo' | 'in-progress' | 'completed') => { + updateTask({ id: props.id, status: newStatus }); + }; + return ( {(provided) => ( - {props.title} - + + {props.title} + {badgeNames[props.status]} + + + + + {props.status !== 'todo' && ( + + )} + {props.status !== 'in-progress' && ( + + )} + {props.status !== 'completed' && ( + + )} )} diff --git a/cool_todo_manager/src/services/mainApi.ts b/cool_todo_manager/src/services/mainApi.ts index f39a73d..3dbcdb8 100644 --- a/cool_todo_manager/src/services/mainApi.ts +++ b/cool_todo_manager/src/services/mainApi.ts @@ -57,7 +57,7 @@ export const mainApi = createApi({ query: (task) => ({ url: `tasks/${task.id}`, method: 'PATCH', - body: task, + body: {status: task.status}, }), invalidatesTags: (result, error, id) => [{ type: 'Task', id }], }), From 8032f415dc7dd4acac137836f24790825d41f97a Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 3 Mar 2025 20:15:12 +0300 Subject: [PATCH 5/5] still rainbow vommit --- backend/src/controllers.ts | 4 +- .../src/components/CardGroup/CardGroup.tsx | 22 ++-- .../src/components/MainBoard/MainBoard.tsx | 30 ++--- .../AddUserToProjectDialog.tsx | 92 +++++++++++++++ .../CreateProjectDialog.tsx | 59 ++++++++++ .../CreateTaskDialog/CreateTaskDialog.tsx | 0 .../DeleteProjectDialog/CreateTaskDialog.tsx | 0 .../pages/auth/RegisterPage/RegisterPage.tsx | 61 +++++++++- cool_todo_manager/src/services/mainApi.ts | 110 ++++++++++++++---- 9 files changed, 324 insertions(+), 54 deletions(-) create mode 100644 cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx create mode 100644 cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx rename cool_todo_manager/src/components/{CardGroup => dialogs}/CreateTaskDialog/CreateTaskDialog.tsx (100%) rename cool_todo_manager/src/components/{CardGroup => dialogs}/DeleteProjectDialog/CreateTaskDialog.tsx (100%) diff --git a/backend/src/controllers.ts b/backend/src/controllers.ts index 427905f..416f6f0 100644 --- a/backend/src/controllers.ts +++ b/backend/src/controllers.ts @@ -1,9 +1,9 @@ import { Request, Response } from 'express'; +import jwt from 'jsonwebtoken'; import { getRepository } from 'typeorm'; import { Project } from './entities/Project'; -import { ProjectMember } from './entities/ProjectMember' +import { ProjectMember } from './entities/ProjectMember'; import { Task } from './entities/Task'; -import jwt from 'jsonwebtoken'; import { User } from './entities/User'; const SECRET_KEY = process.env.JWT_SECRET || 'your_secret_key'; diff --git a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx index fa0ed1d..7185d91 100644 --- a/cool_todo_manager/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/src/components/CardGroup/CardGroup.tsx @@ -6,12 +6,15 @@ import { useGetTasksForGroupQuery, } from '../../services/mainApi'; import TaskCard from '../TaskCard/TaskCard'; -import CreateTaskDialog from './CreateTaskDialog/CreateTaskDialog'; -import DeleteProjectDialog from './DeleteProjectDialog/CreateTaskDialog'; +import AddUserToProjectDialog from '../dialogs/AddUserToProjectDialog/AddUserToProjectDialog'; +import CreateTaskDialog from '../dialogs/CreateTaskDialog/CreateTaskDialog'; +import DeleteProjectDialog from '../dialogs/DeleteProjectDialog/CreateTaskDialog'; + +import { PlusIcon } from '@radix-ui/react-icons'; type TCardGroup = { id: string; - title: string; + title: string; }; export default function CardGroup(props: TCardGroup) { @@ -56,14 +59,19 @@ export default function CardGroup(props: TCardGroup) {
- + - + + + + + )}
diff --git a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx index 94c6331..41854df 100644 --- a/cool_todo_manager/src/components/MainBoard/MainBoard.tsx +++ b/cool_todo_manager/src/components/MainBoard/MainBoard.tsx @@ -2,41 +2,24 @@ import { DragDropContext } from '@hello-pangea/dnd'; import { Button, Flex, ScrollArea } from '@radix-ui/themes'; import { useCreateProjectMutation, - useCreateTaskMutation, - useGetProjectsQuery, + useGetProjectsQuery } from '../../services/mainApi'; import CardGroup from '../CardGroup/CardGroup'; +import CreateProjectDialog from '../dialogs/CreateProjectDialog/CreateProjectDialog'; export default function MainBoard() { const dragEndHandle = (result: TDragResult) => { result; }; - const [create] = useCreateTaskMutation(); const [createProject] = useCreateProjectMutation(); const { data: cringe, isLoading } = useGetProjectsQuery({}); - const onClick = () => { - create({ - title: 'My Task 123', - projectId: 4, - assignedUserId: 1, - deadline: '2025-03-01T12:00:00Z', - }); - }; - - const onClick1 = () => { - createProject({ - title: 'My project test 12', - description: 'Test desc 123 123 123', - }); - }; - return ( <> - - + + {!isLoading && (cringe as any[]).map((item: any) => ( @@ -44,7 +27,10 @@ export default function MainBoard() { - + + + + ); } diff --git a/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx new file mode 100644 index 0000000..65727f8 --- /dev/null +++ b/cool_todo_manager/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx @@ -0,0 +1,92 @@ +import { Button, Dialog, Flex, Select } from '@radix-ui/themes'; +import { PropsWithChildren, useState } from 'react'; +import { + useAddProjectMemberMutation, + useGetAllUsersQuery, +} from '../../../services/mainApi'; + +type TAddUserToProjectDialog = { + projectId: number; +} & PropsWithChildren; + +export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { + const { projectId, children } = props; + const { data: users, isLoading, error, refetch } = useGetAllUsersQuery({}); + const [addProjectMember, { isLoading: isAdding }] = + useAddProjectMemberMutation(); + const [selectedUserId, setSelectedUserId] = useState(null); + + const handleAddUser = async () => { + if (selectedUserId) { + try { + await addProjectMember({ + projectId, + memberId: selectedUserId, + }).unwrap(); + setSelectedUserId(null); + } catch (err) { + console.error('Failed to add user:', err); + } + } + }; + + + return ( + refetch()}> + {children} + + + Add User to Project + + + {isLoading ? ( + Loading users... + ) : error ? ( + Error loading users. + ) : ( + + setSelectedUserId(Number(value)) + } + required + > + + {selectedUserId + ? users?.find( + (user: any) => + user.id === selectedUserId, + )?.username + : 'Select a user'} + + + {users?.map((user: any) => ( + + {user.username} + + ))} + + + )} + + + + + + + + + + + + + ); +} diff --git a/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx b/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx new file mode 100644 index 0000000..8d74bf8 --- /dev/null +++ b/cool_todo_manager/src/components/dialogs/CreateProjectDialog/CreateProjectDialog.tsx @@ -0,0 +1,59 @@ +import { Button, Dialog, Flex, TextArea, TextField } from '@radix-ui/themes'; +import { PropsWithChildren, useState } from 'react'; + +type TCreateProjectDialog = { + onCreate: (f: any) => void; +} & PropsWithChildren; + +export default function CreateProjectDialog(props: TCreateProjectDialog) { + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); + + const handleCreate = () => { + props.onCreate({title, description}); + setTitle(''); + setDescription(''); + }; + + return ( + + + {props.children} + + + + Create a New Project + + + setTitle(e.target.value)} + required + > +