From c0730686acdaa721935b16f4434e72afb7b9f87e Mon Sep 17 00:00:00 2001 From: Max Date: Tue, 8 Apr 2025 15:24:42 +0300 Subject: [PATCH] this is working, need to commit --- cool_todo_manager/mainApp/host/index.html | 14 + cool_todo_manager/mainApp/host/index.tsx | 2 + cool_todo_manager/mainApp/host/package.json | 63 ++++ .../mainApp/host/postcss.config.js | 5 + .../mainApp/host/public/favicon.ico | 0 .../mainApp/host/public/vite.svg | 1 + cool_todo_manager/mainApp/host/src/App.css | 0 cool_todo_manager/mainApp/host/src/App.tsx | 18 + cool_todo_manager/mainApp/host/src/context.ts | 4 + cool_todo_manager/mainApp/host/src/index.css | 5 + cool_todo_manager/mainApp/host/src/main.tsx | 5 + .../mainApp/host/src/vite-env.d.ts | 1 + .../mainApp/host/tailwind.config.js | 7 + cool_todo_manager/mainApp/host/tsconfig.json | 10 + .../mainApp/host/webpack.config.ts | 94 +++++ cool_todo_manager/mainApp/main/package.json | 7 +- cool_todo_manager/mainApp/main/src/App.tsx | 15 +- .../mainApp/main/src/api/RTKQuery.ts | 33 -- .../mainApp/main/src/api/axios.ts | 2 +- .../mainApp/main/src/api/queryClient.ts | 11 - .../components/AuthWrapper/AuthWrapper.tsx | 7 +- .../src/components/CardGroup/CardGroup.tsx | 64 ++-- .../src/components/MainBoard/MainBoard.tsx | 38 ++- .../main/src/components/TaskCard/TaskCard.tsx | 39 ++- .../AddUserToProjectDialog.tsx | 55 ++- cool_todo_manager/mainApp/main/src/context.ts | 4 + .../mainApp/main/src/hooks/useStore.tsx | 8 + cool_todo_manager/mainApp/main/src/index.css | 2 + cool_todo_manager/mainApp/main/src/main.tsx | 2 +- .../src/pages/auth/LoginPage/LoginPage.tsx | 44 +-- .../pages/auth/RegisterPage/RegisterPage.tsx | 36 +- .../mainApp/main/src/pages/main/MainPage.tsx | 8 +- .../mainApp/main/src/services/mainApi.ts | 231 ------------- .../mainApp/main/src/stores/AppStore.ts | 15 + .../mainApp/main/src/stores/AuthStore.ts | 95 ++++++ .../mainApp/main/src/stores/MainStore.ts | 323 ++++++++++++++++++ .../mainApp/main/webpack.config.ts | 3 - cool_todo_manager/package-lock.json | 170 +++++++++ 38 files changed, 1034 insertions(+), 407 deletions(-) create mode 100644 cool_todo_manager/mainApp/host/index.html create mode 100644 cool_todo_manager/mainApp/host/index.tsx create mode 100644 cool_todo_manager/mainApp/host/package.json create mode 100644 cool_todo_manager/mainApp/host/postcss.config.js create mode 100644 cool_todo_manager/mainApp/host/public/favicon.ico create mode 100644 cool_todo_manager/mainApp/host/public/vite.svg create mode 100644 cool_todo_manager/mainApp/host/src/App.css create mode 100644 cool_todo_manager/mainApp/host/src/App.tsx create mode 100644 cool_todo_manager/mainApp/host/src/context.ts create mode 100644 cool_todo_manager/mainApp/host/src/index.css create mode 100644 cool_todo_manager/mainApp/host/src/main.tsx create mode 100644 cool_todo_manager/mainApp/host/src/vite-env.d.ts create mode 100644 cool_todo_manager/mainApp/host/tailwind.config.js create mode 100644 cool_todo_manager/mainApp/host/tsconfig.json create mode 100644 cool_todo_manager/mainApp/host/webpack.config.ts delete mode 100644 cool_todo_manager/mainApp/main/src/api/RTKQuery.ts delete mode 100644 cool_todo_manager/mainApp/main/src/api/queryClient.ts create mode 100644 cool_todo_manager/mainApp/main/src/context.ts create mode 100644 cool_todo_manager/mainApp/main/src/hooks/useStore.tsx delete mode 100644 cool_todo_manager/mainApp/main/src/services/mainApi.ts create mode 100644 cool_todo_manager/mainApp/main/src/stores/AppStore.ts create mode 100644 cool_todo_manager/mainApp/main/src/stores/AuthStore.ts create mode 100644 cool_todo_manager/mainApp/main/src/stores/MainStore.ts diff --git a/cool_todo_manager/mainApp/host/index.html b/cool_todo_manager/mainApp/host/index.html new file mode 100644 index 0000000..e103f53 --- /dev/null +++ b/cool_todo_manager/mainApp/host/index.html @@ -0,0 +1,14 @@ + + + + + + + + Vite + React + TS + + +
+ + + diff --git a/cool_todo_manager/mainApp/host/index.tsx b/cool_todo_manager/mainApp/host/index.tsx new file mode 100644 index 0000000..09e8f97 --- /dev/null +++ b/cool_todo_manager/mainApp/host/index.tsx @@ -0,0 +1,2 @@ +import('./src/main') +export { } diff --git a/cool_todo_manager/mainApp/host/package.json b/cool_todo_manager/mainApp/host/package.json new file mode 100644 index 0000000..d2a3c40 --- /dev/null +++ b/cool_todo_manager/mainApp/host/package.json @@ -0,0 +1,63 @@ +{ + "name": "host", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "start": "webpack serve --env mode=development", + "build:dev": "webpack --env mode=development", + "build:prod": "webpack --env mode=production", + "build:mobile": "webpack --env mode=production --env platform=mobile", + "build:desktop": "webpack --env mode=production --env platform=desktop", + "typecheck": "tsc" + }, + "dependencies": { + "@packages/build-config": "*", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@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", + "@reduxjs/toolkit": "^2.6.1", + "@tailwindcss/postcss": "^4.1.3", + "@tailwindcss/vite": "^4.0.6", + "@tanstack/react-query": "^5.66.0", + "@tanstack/react-query-devtools": "^5.66.0", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.14.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.21", + "axios": "^1.7.9", + "css-loader": "^7.1.2", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "jotai": "^2.12.0", + "js-cookie": "^3.0.5", + "mini-css-extract-plugin": "^2.9.2", + "postcss": "^8.5.3", + "postcss-loader": "^8.1.1", + "react-redux": "^9.2.0", + "style-loader": "^4.0.0", + "tailwindcss": "^4.1.3", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0", + "webpack": "^5.88.2", + "webpack-bundle-analyzer": "^4.9.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.26.2" + } +} diff --git a/cool_todo_manager/mainApp/host/postcss.config.js b/cool_todo_manager/mainApp/host/postcss.config.js new file mode 100644 index 0000000..321b058 --- /dev/null +++ b/cool_todo_manager/mainApp/host/postcss.config.js @@ -0,0 +1,5 @@ +module.exports = { + plugins: [ + require('@tailwindcss/postcss'), + ], + } \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/public/favicon.ico b/cool_todo_manager/mainApp/host/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/cool_todo_manager/mainApp/host/public/vite.svg b/cool_todo_manager/mainApp/host/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/cool_todo_manager/mainApp/host/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/src/App.css b/cool_todo_manager/mainApp/host/src/App.css new file mode 100644 index 0000000..e69de29 diff --git a/cool_todo_manager/mainApp/host/src/App.tsx b/cool_todo_manager/mainApp/host/src/App.tsx new file mode 100644 index 0000000..d93ba49 --- /dev/null +++ b/cool_todo_manager/mainApp/host/src/App.tsx @@ -0,0 +1,18 @@ +import { Theme } from '@radix-ui/themes'; +import '@radix-ui/themes/styles.css'; +import './App.css'; +//@ts-ignore +import MainApp from 'main/AppMain'; + +function App() { + return ( + + I will fuck youuuuuuu!!!!!! +
+ +
+
+ ); +} + +export default App; diff --git a/cool_todo_manager/mainApp/host/src/context.ts b/cool_todo_manager/mainApp/host/src/context.ts new file mode 100644 index 0000000..a994b6a --- /dev/null +++ b/cool_todo_manager/mainApp/host/src/context.ts @@ -0,0 +1,4 @@ +import React from "react"; +import { appStore } from "./stores/AppStore"; + +export const Ctx = React.createContext(appStore); \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/src/index.css b/cool_todo_manager/mainApp/host/src/index.css new file mode 100644 index 0000000..358e675 --- /dev/null +++ b/cool_todo_manager/mainApp/host/src/index.css @@ -0,0 +1,5 @@ +@import 'tailwindcss'; + +#root { + height: 100vh; +} \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/src/main.tsx b/cool_todo_manager/mainApp/host/src/main.tsx new file mode 100644 index 0000000..35f6c8f --- /dev/null +++ b/cool_todo_manager/mainApp/host/src/main.tsx @@ -0,0 +1,5 @@ +import { createRoot } from 'react-dom/client'; +import App from './App.tsx'; +import './index.css'; + +createRoot(document.getElementById('root')!).render(); diff --git a/cool_todo_manager/mainApp/host/src/vite-env.d.ts b/cool_todo_manager/mainApp/host/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/cool_todo_manager/mainApp/host/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/cool_todo_manager/mainApp/host/tailwind.config.js b/cool_todo_manager/mainApp/host/tailwind.config.js new file mode 100644 index 0000000..1f6ade0 --- /dev/null +++ b/cool_todo_manager/mainApp/host/tailwind.config.js @@ -0,0 +1,7 @@ +export const content = [ + './src/**/*.{html,js,tsx}', +]; +export const theme = { + extend: {}, +}; +export const plugins = []; \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/tsconfig.json b/cool_todo_manager/mainApp/host/tsconfig.json new file mode 100644 index 0000000..04a7280 --- /dev/null +++ b/cool_todo_manager/mainApp/host/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist/", + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} \ No newline at end of file diff --git a/cool_todo_manager/mainApp/host/webpack.config.ts b/cool_todo_manager/mainApp/host/webpack.config.ts new file mode 100644 index 0000000..6409142 --- /dev/null +++ b/cool_todo_manager/mainApp/host/webpack.config.ts @@ -0,0 +1,94 @@ +import { BuildMode, BuildPaths, BuildPlatform, buildWebpack } from '@packages/build-config'; +import fs from 'fs'; +import MiniCssExtractPlugin from 'mini-css-extract-plugin'; +import path from 'path'; +import webpack from 'webpack'; +import packageJson from './package.json'; + +interface EnvVariables { + mode?: BuildMode; + analyzer?: boolean; + port?: number; + platform?: BuildPlatform; +} + +export default (env: EnvVariables) => { + const paths: BuildPaths = { + output: path.resolve(__dirname, 'build'), + entry: path.resolve(__dirname, 'index.tsx'), + html: path.resolve(__dirname, 'index.html'), + public: path.resolve(__dirname, 'public'), + src: path.resolve(__dirname, 'src'), + } + + const config: webpack.Configuration = buildWebpack({ + port: env.port ?? 3000, + mode: env.mode ?? 'development', + paths, + analyzer: env.analyzer, + platform: env.platform ?? 'desktop' + }) + + const tailwindConfigPath = path.resolve(__dirname, 'tailwind.config.js'); + if (!fs.existsSync(tailwindConfigPath)) { + console.error('tailwind.config.js does not exist. Please create it or check the path.'); + process.exit(1); + } else { + console.log(` tailwind.config.js found at: ${tailwindConfigPath}`); + } + + const postcssConfigPath = path.resolve(__dirname, 'postcss.config.js'); + if (!fs.existsSync(postcssConfigPath)) { + console.error('postcss.config.js does not exist. Please create it or check the path.'); + process.exit(1); + } else { + console.log(`postcss.config.js found at: ${postcssConfigPath}`); + } + + if(!config.plugins) { + console.error('No plugins found in webpack configuration'); + process.exit(1); + } + + if(config.module && config.module.rules) { + config.module.rules.push({ + test: /\.css$/, + use: [ + env.mode === 'development' ? 'style-loader' : MiniCssExtractPlugin.loader, + 'css-loader', + 'postcss-loader' + ] + }); + } + + + config.plugins.push( + new MiniCssExtractPlugin({ + filename: '[name].[contenthash].css', + }), + new webpack.container.ModuleFederationPlugin({ + name: 'todo_thingy', + filename: 'remoteEntry.js', + remotes: { + main: 'todo_thingy@http://localhost:3001/remoteEntry.js', + }, + shared: { + ...packageJson.dependencies, + 'react': { + eager: true, + requiredVersion: packageJson.dependencies['react'], + }, + 'react-router-dom': { + eager: true, + requiredVersion: packageJson.dependencies['react-router-dom'], + }, + 'react-dom': { + eager: true, + requiredVersion: packageJson.dependencies['react-dom'], + }, + }, + + })) + + return config; +} diff --git a/cool_todo_manager/mainApp/main/package.json b/cool_todo_manager/mainApp/main/package.json index fd04b7e..258ec51 100644 --- a/cool_todo_manager/mainApp/main/package.json +++ b/cool_todo_manager/mainApp/main/package.json @@ -57,6 +57,11 @@ "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.9.1", "webpack-cli": "^5.1.4", - "webpack-dev-server": "^4.15.1" + "webpack-dev-server": "^4.15.1", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-router-dom": "^6.26.2" } } diff --git a/cool_todo_manager/mainApp/main/src/App.tsx b/cool_todo_manager/mainApp/main/src/App.tsx index 0116187..0664bbe 100644 --- a/cool_todo_manager/mainApp/main/src/App.tsx +++ b/cool_todo_manager/mainApp/main/src/App.tsx @@ -1,24 +1,19 @@ 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'; +import { appStore } from './stores/AppStore'; +import { Ctx } from './context'; const router = createBrowserRouter(MyRoutes); function App() { return ( - - - {/* */} - - - + + + ); } diff --git a/cool_todo_manager/mainApp/main/src/api/RTKQuery.ts b/cool_todo_manager/mainApp/main/src/api/RTKQuery.ts deleted file mode 100644 index cd93886..0000000 --- a/cool_todo_manager/mainApp/main/src/api/RTKQuery.ts +++ /dev/null @@ -1,33 +0,0 @@ -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, - [authApi.reducerPath]: authApi.reducer, - }, - middleware: (getDefaultMiddleware) => - getDefaultMiddleware() - .prepend(loggerMiddleware) - .concat(mainApi.middleware) - .concat(authApi.middleware), -}); - -setupListeners(store.dispatch); - diff --git a/cool_todo_manager/mainApp/main/src/api/axios.ts b/cool_todo_manager/mainApp/main/src/api/axios.ts index 7b0f02d..1bfbdd1 100644 --- a/cool_todo_manager/mainApp/main/src/api/axios.ts +++ b/cool_todo_manager/mainApp/main/src/api/axios.ts @@ -9,6 +9,6 @@ export const axiosBase = axios.create({ export const axiosAuth = axios.create({ baseURL: BASE_URL, headers: { - Authorization: `Bearer ${localStorage.getItem('token')}` // Maybe we will use cookies + Authorization: `Bearer ${localStorage.getItem('token')}` } }); \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/api/queryClient.ts b/cool_todo_manager/mainApp/main/src/api/queryClient.ts deleted file mode 100644 index 75e4ae8..0000000 --- a/cool_todo_manager/mainApp/main/src/api/queryClient.ts +++ /dev/null @@ -1,11 +0,0 @@ -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/mainApp/main/src/components/AuthWrapper/AuthWrapper.tsx b/cool_todo_manager/mainApp/main/src/components/AuthWrapper/AuthWrapper.tsx index 07d3bac..b262154 100644 --- a/cool_todo_manager/mainApp/main/src/components/AuthWrapper/AuthWrapper.tsx +++ b/cool_todo_manager/mainApp/main/src/components/AuthWrapper/AuthWrapper.tsx @@ -1,7 +1,8 @@ +import { observer } from 'mobx-react-lite'; import { PropsWithChildren } from 'react'; import { Navigate } from 'react-router-dom'; -export default function AuthWrapper(props: PropsWithChildren) { +const AuthWrapper = observer((props: PropsWithChildren) => { if (!localStorage.getItem('token')) { console.log('No token found, redirecting to login'); @@ -9,4 +10,6 @@ export default function AuthWrapper(props: PropsWithChildren) { } return
{props.children}
; -} +}) + +export default AuthWrapper; \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/components/CardGroup/CardGroup.tsx b/cool_todo_manager/mainApp/main/src/components/CardGroup/CardGroup.tsx index 7185d91..16483a8 100644 --- a/cool_todo_manager/mainApp/main/src/components/CardGroup/CardGroup.tsx +++ b/cool_todo_manager/mainApp/main/src/components/CardGroup/CardGroup.tsx @@ -1,30 +1,34 @@ +import { useEffect } from 'react'; import { Droppable } from '@hello-pangea/dnd'; import { Box, Button, Flex, Text } from '@radix-ui/themes'; -import { - useCreateTaskMutation, - useDeleteProjectMutation, - useGetTasksForGroupQuery, -} from '../../services/mainApi'; +import { PlusIcon } from '@radix-ui/react-icons'; +import { observer } from 'mobx-react-lite'; + import TaskCard from '../TaskCard/TaskCard'; 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'; +import { useStore } from '../../hooks/useStore'; type TCardGroup = { id: string; - title: string; + title: string; }; -export default function CardGroup(props: TCardGroup) { - const { data, isLoading } = useGetTasksForGroupQuery(props.id); - - const [createTaskForGroup] = useCreateTaskMutation(); - const [deleteProject] = useDeleteProjectMutation(); +function CardGroup(props: TCardGroup) { + const store = useStore(); + useEffect(() => { + store.mainStore.fetchTasksForGroup(props.id).catch(console.error); + }, [props.id, store.mainStore]); + + + const tasks = store.mainStore.tasksByProject.get(props.id) ?? []; + const isLoading = store.mainStore.isLoading; + const createTask = (taskText: string, date: string) => { - createTaskForGroup({ + store.mainStore.createTask({ title: taskText, projectId: props.id, assignedUserId: 1, @@ -33,7 +37,7 @@ export default function CardGroup(props: TCardGroup) { }; const deleteGroup = () => { - deleteProject(props.id); + store.mainStore.deleteProject(props.id); }; return ( @@ -41,26 +45,32 @@ export default function CardGroup(props: TCardGroup) { {(provided) => ( {props.title} + - {data && - data.map((task, i) => ( - - ))} + {isLoading && Loading...} + + {!isLoading && tasks.map((task: any, i: number) => ( + + ))} + {provided.placeholder} + + @@ -69,7 +79,7 @@ export default function CardGroup(props: TCardGroup) { @@ -77,3 +87,5 @@ export default function CardGroup(props: TCardGroup) { ); } + +export default observer(CardGroup); diff --git a/cool_todo_manager/mainApp/main/src/components/MainBoard/MainBoard.tsx b/cool_todo_manager/mainApp/main/src/components/MainBoard/MainBoard.tsx index 41854df..9fbcf66 100644 --- a/cool_todo_manager/mainApp/main/src/components/MainBoard/MainBoard.tsx +++ b/cool_todo_manager/mainApp/main/src/components/MainBoard/MainBoard.tsx @@ -1,28 +1,44 @@ +import { useEffect } from 'react'; +import { observer } from 'mobx-react-lite'; import { DragDropContext } from '@hello-pangea/dnd'; import { Button, Flex, ScrollArea } from '@radix-ui/themes'; -import { - useCreateProjectMutation, - useGetProjectsQuery -} from '../../services/mainApi'; + import CardGroup from '../CardGroup/CardGroup'; import CreateProjectDialog from '../dialogs/CreateProjectDialog/CreateProjectDialog'; +import { useStore } from '../../hooks/useStore'; + +function MainBoard() { + const store = useStore(); + + useEffect(() => { + store.mainStore.fetchProjects().catch(console.error); + }, []); -export default function MainBoard() { const dragEndHandle = (result: TDragResult) => { - result; + console.log(result); }; - const [createProject] = useCreateProjectMutation(); - const { data: cringe, isLoading } = useGetProjectsQuery({}); + const createProject = (newProjectData: any) => { + store.mainStore.createProject(newProjectData); + }; + + const cringe = store.mainStore.projects; + const isLoading = store.mainStore.isLoading; return ( <> + {isLoading &&
Loading Projects...
} + {!isLoading && - (cringe as any[]).map((item: any) => ( - + cringe.map((item: any) => ( + ))}
@@ -34,3 +50,5 @@ export default function MainBoard() { ); } + +export default observer(MainBoard); diff --git a/cool_todo_manager/mainApp/main/src/components/TaskCard/TaskCard.tsx b/cool_todo_manager/mainApp/main/src/components/TaskCard/TaskCard.tsx index 83a99dd..d69048e 100644 --- a/cool_todo_manager/mainApp/main/src/components/TaskCard/TaskCard.tsx +++ b/cool_todo_manager/mainApp/main/src/components/TaskCard/TaskCard.tsx @@ -1,6 +1,7 @@ import { Draggable } from '@hello-pangea/dnd'; import { Badge, Button, Card, Flex, Text } from '@radix-ui/themes'; -import { useUpdateTaskMutation } from '../../services/mainApi'; +import { observer } from 'mobx-react-lite'; +import { useStore } from '../../hooks/useStore'; type TTaskCard = { title?: string; @@ -8,26 +9,32 @@ type TTaskCard = { id?: string; index?: number; status: 'todo' | 'in-progress' | 'completed'; + projectId: string; }; const badgeNames = { todo: 'To do', 'in-progress': 'In progress', completed: 'Completed', -} as const +} as const; const badgeColors = { todo: 'blue', 'in-progress': 'orange', completed: 'green', -} as const +} as const; -export default function TaskCard(props: TTaskCard) { - const [updateTask] = useUpdateTaskMutation(); +const TaskCard = observer((props: TTaskCard) => { + const store = useStore() + const updateStatus = (newStatus: 'todo' | 'in-progress' | 'completed') => { - updateTask({ id: props.id, status: newStatus }); + if (!props.id) return; + store.mainStore.updateTask({ id: props.id, status: newStatus }); }; + // @ts-ignore + console.log(`rerendered with`, store.mainStore.tasksByProject.get(2) ); + return ( @@ -36,6 +43,7 @@ export default function TaskCard(props: TTaskCard) { className="!w-62 !flex flex-col gap-2" ref={provided.innerRef} {...provided.draggableProps} + {...provided.dragHandleProps} > @@ -46,17 +54,26 @@ export default function TaskCard(props: TTaskCard) { {props.status !== 'todo' && ( - )} {props.status !== 'in-progress' && ( - )} {props.status !== 'completed' && ( - )} @@ -65,4 +82,6 @@ export default function TaskCard(props: TTaskCard) { )} ); -} +}) + +export default TaskCard; diff --git a/cool_todo_manager/mainApp/main/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx b/cool_todo_manager/mainApp/main/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx index 65727f8..9ea3e8f 100644 --- a/cool_todo_manager/mainApp/main/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx +++ b/cool_todo_manager/mainApp/main/src/components/dialogs/AddUserToProjectDialog/AddUserToProjectDialog.tsx @@ -1,28 +1,28 @@ +import { PropsWithChildren, useEffect, useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Button, Dialog, Flex, Select } from '@radix-ui/themes'; -import { PropsWithChildren, useState } from 'react'; -import { - useAddProjectMemberMutation, - useGetAllUsersQuery, -} from '../../../services/mainApi'; + +import { useStore } from '../../../hooks/useStore'; type TAddUserToProjectDialog = { projectId: number; } & PropsWithChildren; -export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { +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 store = useStore(); + + + useEffect(() => { + store.authStore.fetchAllUsers(); + }, []); + const handleAddUser = async () => { if (selectedUserId) { try { - await addProjectMember({ - projectId, - memberId: selectedUserId, - }).unwrap(); + await store.mainStore.addProjectMember(projectId.toString(), selectedUserId.toString()); setSelectedUserId(null); } catch (err) { console.error('Failed to add user:', err); @@ -30,9 +30,12 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { } }; + const isLoading = store.authStore.isLoading; + const error = store.authStore.error; + const users = store.authStore.allUsers; return ( - refetch()}> + {children} @@ -42,26 +45,21 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { {isLoading ? ( Loading users... ) : error ? ( - Error loading users. + Error loading users: {error} ) : ( - setSelectedUserId(Number(value)) - } + onValueChange={(value) => setSelectedUserId(Number(value))} required > {selectedUserId - ? users?.find( - (user: any) => - user.id === selectedUserId, - )?.username + ? users?.find((user: any) => user.id === selectedUserId)?.username : 'Select a user'} {users?.map((user: any) => ( - + {user.username} ))} @@ -76,13 +74,10 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { Cancel + - @@ -90,3 +85,5 @@ export default function AddUserToProjectDialog(props: TAddUserToProjectDialog) { ); } + +export default observer(AddUserToProjectDialog); diff --git a/cool_todo_manager/mainApp/main/src/context.ts b/cool_todo_manager/mainApp/main/src/context.ts new file mode 100644 index 0000000..a994b6a --- /dev/null +++ b/cool_todo_manager/mainApp/main/src/context.ts @@ -0,0 +1,4 @@ +import React from "react"; +import { appStore } from "./stores/AppStore"; + +export const Ctx = React.createContext(appStore); \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/hooks/useStore.tsx b/cool_todo_manager/mainApp/main/src/hooks/useStore.tsx new file mode 100644 index 0000000..0fe5856 --- /dev/null +++ b/cool_todo_manager/mainApp/main/src/hooks/useStore.tsx @@ -0,0 +1,8 @@ +import { useContext } from "react"; +import { Ctx } from "../context"; + +export function useStore() { + const store = useContext(Ctx) + + return store; +} \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/index.css b/cool_todo_manager/mainApp/main/src/index.css index b953eba..358e675 100644 --- a/cool_todo_manager/mainApp/main/src/index.css +++ b/cool_todo_manager/mainApp/main/src/index.css @@ -1,3 +1,5 @@ +@import 'tailwindcss'; + #root { height: 100vh; } \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/main.tsx b/cool_todo_manager/mainApp/main/src/main.tsx index 5a02646..35f6c8f 100644 --- a/cool_todo_manager/mainApp/main/src/main.tsx +++ b/cool_todo_manager/mainApp/main/src/main.tsx @@ -1,5 +1,5 @@ import { createRoot } from 'react-dom/client'; -import App from './App'; +import App from './App.tsx'; import './index.css'; createRoot(document.getElementById('root')!).render(); diff --git a/cool_todo_manager/mainApp/main/src/pages/auth/LoginPage/LoginPage.tsx b/cool_todo_manager/mainApp/main/src/pages/auth/LoginPage/LoginPage.tsx index 76acf09..c465cbe 100644 --- a/cool_todo_manager/mainApp/main/src/pages/auth/LoginPage/LoginPage.tsx +++ b/cool_todo_manager/mainApp/main/src/pages/auth/LoginPage/LoginPage.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Box, Button, @@ -8,18 +10,24 @@ import { TextField, } from '@radix-ui/themes'; import { Form } from 'radix-ui'; -import React from 'react'; -import { useLoginMutation } from '../../../services/mainApi'; -export default function LoginPage() { - const [login, { isError }] = useLoginMutation(); +// Импортируем store.authStore +import { useStore } from '../../../hooks/useStore'; - const [username, setUsername] = React.useState(''); - const [password, setPassword] = React.useState(''); - const submitHandler = (e: React.FormEvent) => { +function LoginPage() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + + const store = useStore(); + // В RTK Query было: [login, { isError } ] = useLoginMutation(); + // Теперь берем store.authStore.error, store.authStore.isLoading + const isError = !!store.authStore.error; + const isLoading = store.authStore.isLoading; + + const submitHandler = async (e: React.FormEvent) => { e.preventDefault(); - login({ username, password }); + await store.authStore.login({ username, password }).catch(() => {}); }; return ( @@ -29,15 +37,11 @@ export default function LoginPage() { Login - + Username is required - Password is required - {isError && ( - {'Unable to login. Please try again.'} + {store.authStore.error || 'Unable to login. Please try again.'} )} - - + Register @@ -88,3 +88,5 @@ export default function LoginPage() { ); } + +export default observer(LoginPage); diff --git a/cool_todo_manager/mainApp/main/src/pages/auth/RegisterPage/RegisterPage.tsx b/cool_todo_manager/mainApp/main/src/pages/auth/RegisterPage/RegisterPage.tsx index a23a0fb..3750683 100644 --- a/cool_todo_manager/mainApp/main/src/pages/auth/RegisterPage/RegisterPage.tsx +++ b/cool_todo_manager/mainApp/main/src/pages/auth/RegisterPage/RegisterPage.tsx @@ -1,3 +1,5 @@ +import { useState } from 'react'; +import { observer } from 'mobx-react-lite'; import { Box, Button, @@ -8,17 +10,23 @@ import { TextField, } from '@radix-ui/themes'; import { Form } from 'radix-ui'; -import { useState } from 'react'; -import { useRegisterMutation } from '../../../services/mainApi'; -export default function RegisterPage() { - const [register, { isLoading, error }] = useRegisterMutation(); +// Импортируем store.authStore +import { useStore } from '../../../hooks/useStore'; + + +function RegisterPage() { const [formData, setFormData] = useState({ username: '', email: '', password: '', }); + const store = useStore(); + + const isLoading = store.authStore.isLoading; + const error = store.authStore.error; + const handleChange = (e: React.ChangeEvent) => { setFormData({ ...formData, @@ -29,7 +37,7 @@ export default function RegisterPage() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - await register(formData).unwrap(); + await store.authStore.register(formData); } catch (err) { console.error('Failed to register:', err); } @@ -40,15 +48,11 @@ export default function RegisterPage() { Register - + Username is required - Email is required - Email is not valid - Password is required - {error && ( - Error registering user. + Error registering user: {error} )} @@ -122,4 +124,6 @@ export default function RegisterPage() { ); -} \ No newline at end of file +} + +export default observer(RegisterPage); diff --git a/cool_todo_manager/mainApp/main/src/pages/main/MainPage.tsx b/cool_todo_manager/mainApp/main/src/pages/main/MainPage.tsx index e03c3e5..2f6d8ee 100644 --- a/cool_todo_manager/mainApp/main/src/pages/main/MainPage.tsx +++ b/cool_todo_manager/mainApp/main/src/pages/main/MainPage.tsx @@ -1,10 +1,14 @@ import { Box } from '@radix-ui/themes' +import { observer } from 'mobx-react-lite' import { Outlet } from 'react-router-dom' -export default function MainPage() { +const MainPage = observer(() => { + return ( ) -} +}) + +export default MainPage \ No newline at end of file diff --git a/cool_todo_manager/mainApp/main/src/services/mainApi.ts b/cool_todo_manager/mainApp/main/src/services/mainApi.ts deleted file mode 100644 index ddf181c..0000000 --- a/cool_todo_manager/mainApp/main/src/services/mainApi.ts +++ /dev/null @@ -1,231 +0,0 @@ -import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; - -export const mainApi = createApi({ - reducerPath: 'api', - baseQuery: fetchBaseQuery({ - baseUrl: 'http://109.107.166.17:5000/api/', - prepareHeaders: (headers) => { - headers.set( - 'Authorization', - `Bearer ${localStorage.getItem('token')}`, - ); - return headers; - }, - }), - tagTypes: ['Task', 'Project', 'ProjectMembers'], - 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' }], - }), - 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}`, - method: 'GET', - }), - providesTags: (result, error, id) => [{ type: 'Task', id }], - }), - updateTask: builder.mutation({ - query: (task) => ({ - url: `tasks/${task.id}`, - method: 'PATCH', - body: { status: task.status }, - }), - 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: 'projects/create', - method: 'POST', - body: newProject, - }), - invalidatesTags: [{ type: 'Project', id: 'LIST' }], - }), - getProjects: builder.query({ - query: () => ({ - url: 'projects/my', - 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: 'LIST' }, - ], - }), - - // PROJECT MEMBERS - addProjectMember: builder.mutation({ - query: ({ projectId, memberId }) => ({ - url: `projects/${projectId}/members/add`, - method: 'POST', - body: { userId: memberId, role: '' }, - }), - invalidatesTags: (result, error, args) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - getProjectMembers: builder.query({ - query: (id) => ({ - url: `projects/${id}/members`, - method: 'GET', - }), - providesTags: (result, error, args) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - removeProjectMember: builder.mutation({ - query: ({ projectId, memberId }) => ({ - url: `projects/${projectId}/members/remove`, - method: 'DELETE', - body: { userId: memberId }, - }), - invalidatesTags: (result, error, id) => [ - { type: 'ProjectMembers', id: 'LIST' }, - ], - }), - }), -}); - -export const authApi = createApi({ - reducerPath: 'authApi', - baseQuery: fetchBaseQuery({ - baseUrl: 'http://109.107.166.17:4000/api/', - }), - endpoints: (builder) => ({ - login: builder.mutation({ - query: (credentials) => ({ - url: '/auth/login', - method: 'POST', - body: credentials, - }), - async onQueryStarted(arg, { queryFulfilled }) { - try { - const response = await queryFulfilled; - if (response.data.access_token) { - localStorage.setItem( - 'token', - response.data.access_token, - ); - if (response.meta?.response?.status === 201) - window.location.href = '/'; - } - } catch (error) {} - }, - }), - register: builder.mutation({ - query: (credentials) => ({ - url: '/auth/register', - method: 'POST', - body: credentials, - }), - onQueryStarted: async (arg, { queryFulfilled }) => { - try { - const response = await queryFulfilled; - if (response.meta?.response?.status === 201) - window.location.href = '/login'; - } catch (error) {} - }, - }), - - // USERS - getAllUsers: builder.query({ - query: () => ({ - url: '/users', - method: 'GET', - }), - keepUnusedDataFor: 0, - - }), - }), -}); - -export const { useLoginMutation, useRegisterMutation, useGetAllUsersQuery } = - authApi; - -export const { - useGetTasksQuery, - useCreateTaskMutation, - useUpdateTaskMutation, - useDeleteTaskMutation, - useGetTasksForGroupQuery, -} = mainApi; - -export const { - useGetProjectsQuery, - useCreateProjectMutation, - useUpdateProjectMutation, - useDeleteProjectMutation, -} = mainApi; - -export const { - useRemoveProjectMemberMutation, - useAddProjectMemberMutation, - useGetProjectMembersQuery, -} = mainApi; diff --git a/cool_todo_manager/mainApp/main/src/stores/AppStore.ts b/cool_todo_manager/mainApp/main/src/stores/AppStore.ts new file mode 100644 index 0000000..4ec2c0b --- /dev/null +++ b/cool_todo_manager/mainApp/main/src/stores/AppStore.ts @@ -0,0 +1,15 @@ +import { makeAutoObservable } from 'mobx'; +import { MainStore } from './MainStore'; +import { AuthStore } from './AuthStore'; + +export class AppStore { + mainStore: MainStore + authStore: AuthStore + + constructor() { + this.mainStore = new MainStore(this) + this.authStore = new AuthStore(this) + makeAutoObservable(this); + } +} +export const appStore = new AppStore() diff --git a/cool_todo_manager/mainApp/main/src/stores/AuthStore.ts b/cool_todo_manager/mainApp/main/src/stores/AuthStore.ts new file mode 100644 index 0000000..947e93e --- /dev/null +++ b/cool_todo_manager/mainApp/main/src/stores/AuthStore.ts @@ -0,0 +1,95 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import axios from 'axios'; +import { AppStore } from './AppStore'; + + +export class AuthStore { + appStore: AppStore + + isLoading = false; + error: string | null = null; + + allUsers: any[] = []; + + constructor(appStore:any) { + this.appStore = appStore + makeAutoObservable(this); + } + + get token() { + return localStorage.getItem('token') || ''; + } + + async login(credentials: { username: string; password: string }) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.post( + 'http://109.107.166.17:4000/api/auth/login', + credentials, + ); + + runInAction(() => { + if (response.data.access_token) { + localStorage.setItem('token', response.data.access_token); + window.location.href = '/'; + } + this.isLoading = false; + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при логине'; + this.isLoading = false; + }); + throw error; + } + } + + async register(credentials: { username: string; password: string }) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.post( + 'http://109.107.166.17:4000/api/auth/register', + credentials, + ); + + runInAction(() => { + if (response.status === 201) { + window.location.href = '/login'; + } + this.isLoading = false; + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при регистрации'; + this.isLoading = false; + }); + throw error; + } + } + + async fetchAllUsers(force = false) { + if (this.allUsers.length && !force) { + return this.allUsers; + } + + this.isLoading = true; + this.error = null; + + try { + const response = await axios.get('http://109.107.166.17:4000/api/users'); + runInAction(() => { + this.allUsers = response.data; + this.isLoading = false; + }); + return this.allUsers; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении пользователей'; + this.isLoading = false; + }); + throw error; + } + } +} diff --git a/cool_todo_manager/mainApp/main/src/stores/MainStore.ts b/cool_todo_manager/mainApp/main/src/stores/MainStore.ts new file mode 100644 index 0000000..65d370d --- /dev/null +++ b/cool_todo_manager/mainApp/main/src/stores/MainStore.ts @@ -0,0 +1,323 @@ +import { makeAutoObservable, runInAction } from 'mobx'; +import axios from 'axios'; +import { AppStore } from './AppStore'; + +export class MainStore { + appStore: AppStore + + isLoading = false; + error: string | null = null; + + + tasks: any[] = []; + tasksByProject = new Map(); + + projects: any[] = []; + projectById = new Map(); + projectMembersById = new Map(); + + constructor(appStore:any) { + this.appStore = appStore + makeAutoObservable(this); + } + + + get token() { + return localStorage.getItem('token') || ''; + } + + + get headers() { + return { + Authorization: `Bearer ${this.token}`, + }; + } + + async fetchTasksForGroup(projectId: string, force = false) { + if (this.tasksByProject.has(projectId) && !force) { + return this.tasksByProject.get(projectId); + } + console.log(`This is tasks for ${projectId}`); + + + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/tasks/project/${projectId}`, + { + headers: this.headers, + }, + ); + runInAction(() => { + this.tasksByProject.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при загрузке задач группы'; + this.isLoading = false; + }); + throw error; + } + } + + async fetchTask(taskId: string) { + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/tasks/${taskId}`, + { headers: this.headers }, + ); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при загрузке задачи'; + this.isLoading = false; + }); + throw error; + } finally { + runInAction(() => { + this.isLoading = false; + }); + } + } + + async createTask(newTask: any) { + try { + const response = await axios.post( + 'http://109.107.166.17:5000/api/tasks/create', + newTask, + { headers: this.headers }, + ); + const createdTask = response.data; + this.tasks.push(createdTask); + const oldTasks = this.tasksByProject.get(newTask.projectId) ?? []; + this.tasksByProject.set(newTask.projectId, [...oldTasks, createdTask]); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при создании задачи'; + }); + throw error; + } + } + + async updateTask(task: { id: string; status: string }) { + try { + const response = await axios.patch( + `http://109.107.166.17:5000/api/tasks/${task.id}`, + { status: task.status }, + { headers: this.headers }, + ); + runInAction(() => { + const index = this.tasks.findIndex((t) => t.id === task.id); + if (index !== -1) { + this.tasks[index].status = task.status; + } + const projectId = response.data.project.id; + const oldArray = this.tasksByProject.get(projectId) || []; + const indexInMap = oldArray.findIndex((t) => t.id === +task.id); + if (indexInMap !== -1) { + oldArray[indexInMap].status = task.status; + this.tasksByProject.set(projectId, [...oldArray]); + } + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при обновлении задачи'; + }); + throw error; + } + } + + + async deleteTask(taskId: string) { + try { + await axios.delete(`http://109.107.166.17:5000/api/tasks/${taskId}`, { + headers: this.headers, + }); + // Удаляем локально + runInAction(() => { + this.tasks = this.tasks.filter((t) => t.id !== taskId); + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении задачи'; + }); + throw error; + } + } + + + async fetchProjects(force = false) { + if (this.projects.length && !force) { + return this.projects; + } + this.isLoading = true; + this.error = null; + + try { + const response = await axios.get( + 'http://109.107.166.17:5000/api/projects/my', + { headers: this.headers }, + ); + runInAction(() => { + this.projects = response.data; + this.isLoading = false; + }); + return this.projects; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении проектов'; + this.isLoading = false; + }); + throw error; + } + } + + async fetchProject(projectId: string) { + if (this.projectById.has(projectId)) { + return this.projectById.get(projectId); + } + + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/projects/${projectId}`, + { headers: this.headers }, + ); + runInAction(() => { + this.projectById.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении проекта'; + this.isLoading = false; + }); + throw error; + } + } + + async createProject(newProject: any) { + try { + const response = await axios.post( + 'http://109.107.166.17:5000/api/projects/create', + newProject, + { headers: this.headers }, + ); + const created = response.data; + runInAction(() => { + this.projects.push(created); + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при создании проекта'; + }); + throw error; + } + } + + async updateProject(id: string, projectData: any) { + try { + await axios.patch( + `http://109.107.166.17:5000/api/projects/${id}`, + projectData, + { headers: this.headers }, + ); + this.fetchProjects(true); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при обновлении проекта'; + }); + throw error; + } + } + + async deleteProject(id: string) { + try { + await axios.delete(`http://109.107.166.17:5000/api/projects/${id}`, { + headers: this.headers, + }); + // Удаляем локально + runInAction(() => { + this.projects = this.projects.filter((p) => p.id !== id); + }); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении проекта'; + }); + throw error; + } + } + + async addProjectMember(projectId: string, memberId: string) { + try { + await axios.post( + `http://109.107.166.17:5000/api/projects/${projectId}/members/add`, + { + userId: memberId, + role: '', + }, + { headers: this.headers }, + ); + this.projectMembersById.delete(projectId); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при добавлении участника'; + }); + throw error; + } + } + async fetchProjectMembers(projectId: string, force = false) { + if (this.projectMembersById.has(projectId) && !force) { + return this.projectMembersById.get(projectId); + } + this.isLoading = true; + this.error = null; + try { + const response = await axios.get( + `http://109.107.166.17:5000/api/projects/${projectId}/members`, + { + headers: this.headers, + }, + ); + runInAction(() => { + this.projectMembersById.set(projectId, response.data); + this.isLoading = false; + }); + return response.data; + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при получении участников проекта'; + this.isLoading = false; + }); + throw error; + } + } + + async removeProjectMember(projectId: string, memberId: string) { + try { + await axios.delete( + `http://109.107.166.17:5000/api/projects/${projectId}/members/remove`, + { + data: { userId: memberId }, + headers: this.headers, + }, + ); + + this.projectMembersById.delete(projectId); + } catch (error: any) { + runInAction(() => { + this.error = error?.response?.data?.message || 'Ошибка при удалении участника'; + }); + throw error; + } + } +} + + diff --git a/cool_todo_manager/mainApp/main/webpack.config.ts b/cool_todo_manager/mainApp/main/webpack.config.ts index a0f689b..414a7b0 100644 --- a/cool_todo_manager/mainApp/main/webpack.config.ts +++ b/cool_todo_manager/mainApp/main/webpack.config.ts @@ -29,7 +29,6 @@ export default (env: EnvVariables) => { platform: env.platform ?? 'desktop' }) - // Check if tailwind.config.js exists const tailwindConfigPath = path.resolve(__dirname, 'tailwind.config.js'); if (!fs.existsSync(tailwindConfigPath)) { console.error('tailwind.config.js does not exist. Please create it or check the path.'); @@ -38,7 +37,6 @@ export default (env: EnvVariables) => { console.log(` tailwind.config.js found at: ${tailwindConfigPath}`); } - // Check if postcss.config.js exists const postcssConfigPath = path.resolve(__dirname, 'postcss.config.js'); if (!fs.existsSync(postcssConfigPath)) { console.error('postcss.config.js does not exist. Please create it or check the path.'); @@ -52,7 +50,6 @@ export default (env: EnvVariables) => { process.exit(1); } - // Add Tailwind configuration to build plugins if(config.module && config.module.rules) { config.module.rules.push({ test: /\.css$/, diff --git a/cool_todo_manager/package-lock.json b/cool_todo_manager/package-lock.json index 2e079c9..929e1b6 100644 --- a/cool_todo_manager/package-lock.json +++ b/cool_todo_manager/package-lock.json @@ -12,6 +12,125 @@ "packages/*" ] }, + "mainApp/host": { + "version": "1.0.0", + "dependencies": { + "@packages/build-config": "*", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^6.26.2" + }, + "devDependencies": { + "@eslint/js": "^9.19.0", + "@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", + "@reduxjs/toolkit": "^2.6.1", + "@tailwindcss/postcss": "^4.1.3", + "@tailwindcss/vite": "^4.0.6", + "@tanstack/react-query": "^5.66.0", + "@tanstack/react-query-devtools": "^5.66.0", + "@types/js-cookie": "^3.0.6", + "@types/node": "^22.14.0", + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.4", + "autoprefixer": "^10.4.21", + "axios": "^1.7.9", + "css-loader": "^7.1.2", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.18", + "globals": "^15.14.0", + "jotai": "^2.12.0", + "js-cookie": "^3.0.5", + "mini-css-extract-plugin": "^2.9.2", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", + "postcss": "^8.5.3", + "postcss-loader": "^8.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-redux": "^9.2.0", + "react-router-dom": "^6.26.2", + "style-loader": "^4.0.0", + "tailwindcss": "^4.1.3", + "typescript": "~5.7.2", + "typescript-eslint": "^8.22.0", + "vite": "^6.1.0", + "webpack": "^5.88.2", + "webpack-bundle-analyzer": "^4.9.1", + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.1" + } + }, + "mainApp/host/node_modules/css-loader": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-7.1.2.tgz", + "integrity": "sha512-6WvYYn7l/XEGN8Xu2vWFt9nVzrCn39vKyTEFf/ExEyoksJjjSZV/0/35XPlMbpnr6VGhZIUg5yJrL8tGfes/FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "icss-utils": "^5.1.0", + "postcss": "^8.4.33", + "postcss-modules-extract-imports": "^3.1.0", + "postcss-modules-local-by-default": "^4.0.5", + "postcss-modules-scope": "^3.2.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || 1.x", + "webpack": "^5.27.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "mainApp/host/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "mainApp/host/node_modules/style-loader": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz", + "integrity": "sha512-1V4WqhhZZgjVAVJyt7TdDPZoPBPNHbekX4fWnCJL1yQukhCeZhJySUL+gL9y6sNdN95uEOS83Y55SqHcP7MzLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.27.0" + } + }, "mainApp/main": { "version": "1.0.0", "license": "ISC", @@ -47,9 +166,14 @@ "jotai": "^2.12.0", "js-cookie": "^3.0.5", "mini-css-extract-plugin": "^2.9.2", + "mobx": "^6.13.7", + "mobx-react-lite": "^4.1.0", "postcss": "^8.5.3", "postcss-loader": "^8.1.1", + "react": "^19.0.0", + "react-dom": "^19.0.0", "react-redux": "^9.2.0", + "react-router-dom": "^6.26.2", "style-loader": "^4.0.0", "tailwindcss": "^4.1.3", "typescript": "~5.7.2", @@ -4549,6 +4673,7 @@ "version": "1.23.0", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.23.0.tgz", "integrity": "sha512-O3rHJzAQKamUz1fvE0Qaw0xSFqsA/yafi2iqeE0pvdFtCO1viYx8QL6f3Ln/aCCTLxs68SLf0KPM9eSeM8yBnA==", + "dev": true, "license": "MIT", "engines": { "node": ">=14.0.0" @@ -8860,6 +8985,10 @@ "he": "bin/he" } }, + "node_modules/host": { + "resolved": "mainApp/host", + "link": true + }, "node_modules/hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", @@ -10181,6 +10310,43 @@ "node": "*" } }, + "node_modules/mobx": { + "version": "6.13.7", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.13.7.tgz", + "integrity": "sha512-aChaVU/DO5aRPmk1GX8L+whocagUUpBQqoPtJk+cm7UOXUk87J4PeWCh6nNmTTIfEhiR9DI/+FnA8dln/hTK7g==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + } + }, + "node_modules/mobx-react-lite": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.1.0.tgz", + "integrity": "sha512-QEP10dpHHBeQNv1pks3WnHRCem2Zp636lq54M2nKO2Sarr13pL4u6diQXf65yzXUn0mkk18SyIDCm9UOJYTi1w==", + "dev": true, + "license": "MIT", + "dependencies": { + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mobx" + }, + "peerDependencies": { + "mobx": "^6.9.0", + "react": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/mrmime": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", @@ -11179,6 +11345,7 @@ "version": "19.1.0", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "dev": true, "license": "MIT", "dependencies": { "scheduler": "^0.26.0" @@ -11285,6 +11452,7 @@ "version": "6.30.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", "integrity": "sha512-D3X8FyH9nBcTSHGdEKurK7r8OYE1kKFn3d/CF+CoxbSHkxU7o37+Uh7eAHRXr6k2tSExXYO++07PeXJtA/dEhQ==", + "dev": true, "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0" @@ -11300,6 +11468,7 @@ "version": "6.30.0", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.30.0.tgz", "integrity": "sha512-x30B78HV5tFk8ex0ITwzC9TTZMua4jGyA9IUlH1JLQYQTFyxr/ZxwOJq7evg1JX1qGVUcvhsmQSKdPncQrjTgA==", + "dev": true, "license": "MIT", "dependencies": { "@remix-run/router": "1.23.0", @@ -11719,6 +11888,7 @@ "version": "0.26.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz", "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "dev": true, "license": "MIT" }, "node_modules/schema-utils": {