From 877a55a3a8e53e371623866c3b8f8a3465e2e940 Mon Sep 17 00:00:00 2001 From: SasukeUchiha Date: Fri, 21 Jul 2023 00:01:46 +0300 Subject: [PATCH] V 0.02 --- README.md | 10 +- client/package-lock.json | 190 ++++++++++++++---- client/package.json | 5 +- client/src/App.tsx | 25 ++- .../src/components/Auth/OnlyUnauthorized.tsx | 13 +- client/src/components/Auth/RequireAuth.tsx | 13 +- client/src/components/Index/Sidebar/Menu.tsx | 15 +- client/src/components/Index/UserInfo.tsx | 18 +- client/src/components/LoginForm.tsx | 26 ++- client/src/components/RegisterForm.tsx | 23 ++- client/src/hooks/redux.ts | 5 + client/src/main.tsx | 16 +- client/src/models/response/ServerError.ts | 3 + client/src/store/reducers/UserSlice.ts | 140 +++++++++++++ client/src/store/store.ts | 95 ++------- 15 files changed, 410 insertions(+), 187 deletions(-) create mode 100644 client/src/hooks/redux.ts create mode 100644 client/src/models/response/ServerError.ts create mode 100644 client/src/store/reducers/UserSlice.ts diff --git a/README.md b/README.md index 35e339d..c4edfa0 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,14 @@ Ararat International School - платформа по обучению игры ### Стэк: - React.js -- Express.js -- Tailwind css - TypeScript -- Mobx (временно) +- Redux toolkit +- Tailwind css +- Express.js +- Mongo DB + +#### V 0.02 +- Mobx заменен Redux toolkit #### V 0.01 - Добавлена главная страница diff --git a/client/package-lock.json b/client/package-lock.json index 45a6e7f..d8236a7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -8,14 +8,15 @@ "name": "araratchess", "version": "0.0.0", "dependencies": { + "@reduxjs/toolkit": "^1.9.5", "@types/axios": "^0.14.0", + "@types/react-redux": "^7.1.25", "@types/react-router-dom": "^5.3.3", "axios": "^1.4.0", - "mobx": "^6.9.1", - "mobx-react-lite": "^4.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.45.2", + "react-redux": "^8.1.1", "react-router-dom": "^6.14.2" }, "devDependencies": { @@ -366,6 +367,17 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.6.tgz", + "integrity": "sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==", + "dependencies": { + "regenerator-runtime": "^0.13.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.22.5", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", @@ -960,6 +972,29 @@ "node": ">= 8" } }, + "node_modules/@reduxjs/toolkit": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz", + "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==", + "dependencies": { + "immer": "^9.0.21", + "redux": "^4.2.1", + "redux-thunk": "^2.4.2", + "reselect": "^4.1.8" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.0.2" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@remix-run/router": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz", @@ -982,6 +1017,15 @@ "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz", + "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/json-schema": { "version": "7.0.12", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", @@ -1007,11 +1051,22 @@ "version": "18.2.7", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz", "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/react": "*" } }, + "node_modules/@types/react-redux": { + "version": "7.1.25", + "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz", + "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.0", + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0", + "redux": "^4.0.0" + } + }, "node_modules/@types/react-router": { "version": "5.1.20", "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", @@ -1042,6 +1097,11 @@ "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", "dev": true }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "5.62.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", @@ -2341,6 +2401,14 @@ "node": ">=4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -2350,6 +2418,15 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "9.0.21", + "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz", + "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -2644,39 +2721,6 @@ "node": "*" } }, - "node_modules/mobx": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/mobx/-/mobx-6.9.1.tgz", - "integrity": "sha512-7V41hvBmzCQg5n/xoZi2qkhREbnF3wHbzbIdR1sLJhVt6yWq2Zq0FlNahEt/RccOmEIjipvBqeQ4mlOEyMhaaA==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - } - }, - "node_modules/mobx-react-lite": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/mobx-react-lite/-/mobx-react-lite-4.0.3.tgz", - "integrity": "sha512-wEE1oT5zvDdvplG4HnRrFgPwg5GFVVrEtl42Er85k23zeu3om8H8wbDPgdbQP88zAihVsik6xJfw6VnzUl8fQw==", - "dependencies": { - "use-sync-external-store": "^1.2.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mobx" - }, - "peerDependencies": { - "mobx": "^6.9.0", - "react": "^16.8.0 || ^17 || ^18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - }, - "react-native": { - "optional": true - } - } - }, "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -3124,6 +3168,54 @@ "react": "^16.8.0 || ^17 || ^18" } }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, + "node_modules/react-redux": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz", + "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -3184,6 +3276,32 @@ "node": ">=8.10.0" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, + "node_modules/redux-thunk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz", + "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==", + "peerDependencies": { + "redux": "^4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, + "node_modules/reselect": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", + "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==" + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", diff --git a/client/package.json b/client/package.json index 1b99a77..b852158 100644 --- a/client/package.json +++ b/client/package.json @@ -10,14 +10,15 @@ "preview": "vite preview" }, "dependencies": { + "@reduxjs/toolkit": "^1.9.5", "@types/axios": "^0.14.0", + "@types/react-redux": "^7.1.25", "@types/react-router-dom": "^5.3.3", "axios": "^1.4.0", - "mobx": "^6.9.1", - "mobx-react-lite": "^4.0.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-hook-form": "^7.45.2", + "react-redux": "^8.1.1", "react-router-dom": "^6.14.2" }, "devDependencies": { diff --git a/client/src/App.tsx b/client/src/App.tsx index 980a84f..8d77d04 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,28 +1,33 @@ -import { useEffect, useContext } from "react" +import { useEffect } from "react" import RequireAuth from "./components/Auth/RequireAuth" import OnlyUnauthorized from "./components/Auth/OnlyUnauthorized" import IndexPage from "./pages/IndexPage" import LoginForm from "./components/LoginForm" import RegisterForm from "./components/RegisterForm" import { BrowserRouter, Routes, Route } from 'react-router-dom' -import { Context } from "./main" -import { observer } from "mobx-react-lite" +import { checkAuth, userSlice } from "./store/reducers/UserSlice"; +import { useAppDispatch, useAppSelector } from "./hooks/redux" -function App() { - const {store} = useContext(Context); +function App() { + const {isLoading} = useAppSelector(state => state.UserSlice); + const {storeLoad} = userSlice.actions + const dispatch = useAppDispatch(); + useEffect(()=>{ const CheckLogin = async () => { if(localStorage.getItem('token')) { - await store.checkAuth(); + await dispatch(checkAuth()); } else { - store.storeLoad(); + dispatch(storeLoad()); } } CheckLogin().catch(console.error); - }, []) + }, [dispatch, storeLoad]) + + - if(!store.isLoading) { + if(!isLoading) { return ( @@ -35,4 +40,4 @@ function App() { } } -export default observer(App) +export default App diff --git a/client/src/components/Auth/OnlyUnauthorized.tsx b/client/src/components/Auth/OnlyUnauthorized.tsx index 61502f9..7f6c15f 100644 --- a/client/src/components/Auth/OnlyUnauthorized.tsx +++ b/client/src/components/Auth/OnlyUnauthorized.tsx @@ -1,18 +1,17 @@ -import { FC, PropsWithChildren, useContext } from 'react' +import { FC, PropsWithChildren } from 'react' import { useLocation, Navigate } from 'react-router-dom'; -import { Context } from "../../main" -import { observer } from "mobx-react-lite" +import { useAppSelector } from '../../hooks/redux'; -const OnlyUnauthorized: FC = observer(({children}) => { +const OnlyUnauthorized: FC = ({children}) => { - const {store} = useContext(Context); + const {isAuth} = useAppSelector(state => state.UserSlice); const location = useLocation(); - if (store.isAuth) { + if (isAuth) { return ; } return children; -}) +} export default OnlyUnauthorized; \ No newline at end of file diff --git a/client/src/components/Auth/RequireAuth.tsx b/client/src/components/Auth/RequireAuth.tsx index 8407f67..e239383 100644 --- a/client/src/components/Auth/RequireAuth.tsx +++ b/client/src/components/Auth/RequireAuth.tsx @@ -1,18 +1,17 @@ -import { FC, PropsWithChildren, useContext } from 'react' +import { FC, PropsWithChildren } from 'react' import { useLocation, Navigate } from 'react-router-dom'; -import { Context } from "../../main" -import { observer } from "mobx-react-lite" +import { useAppSelector } from '../../hooks/redux'; -const RequireAuth: FC = observer(({children}) => { +const RequireAuth: FC = ({children}) => { - const {store} = useContext(Context); + const {isAuth} = useAppSelector(state => state.UserSlice); const location = useLocation(); - if (!store.isAuth) { + if (!isAuth) { return ; } return children; -}) +} export default RequireAuth; \ No newline at end of file diff --git a/client/src/components/Index/Sidebar/Menu.tsx b/client/src/components/Index/Sidebar/Menu.tsx index 26a57a0..525a9ae 100644 --- a/client/src/components/Index/Sidebar/Menu.tsx +++ b/client/src/components/Index/Sidebar/Menu.tsx @@ -1,10 +1,17 @@ -import { FC, useContext } from 'react' +import { FC } from 'react' import MenuItem from './MenuItem'; import { Link } from 'react-router-dom'; -import { Context } from '../../../main'; +import { logout } from "../../../store/reducers/UserSlice"; +import { useAppDispatch } from "../../../hooks/redux" const Menu: FC = () => { - const {store} = useContext(Context); + + const dispatch = useAppDispatch(); + + const logoutHandler = async () => { + await dispatch(logout()); + } + return ( ) diff --git a/client/src/components/Index/UserInfo.tsx b/client/src/components/Index/UserInfo.tsx index 02ff946..2521391 100644 --- a/client/src/components/Index/UserInfo.tsx +++ b/client/src/components/Index/UserInfo.tsx @@ -1,21 +1,19 @@ -import { useContext } from "react" import { FC } from 'react' -import { Context } from "../../main" -import { observer } from "mobx-react-lite" +import { useAppSelector } from '../../hooks/redux'; - -const UserInfo: FC = observer(() => { - const {store} = useContext(Context); +const UserInfo: FC = () => { + + const {user} = useAppSelector(state => state.UserSlice); return (
-
avatar
+
avatar
-

{store.user.name + ' ' + store.user.sname}

-

{store.user.email}

+

{user.name + ' ' + user.sname}

+

{user.email}

) -}) +} export default UserInfo; \ No newline at end of file diff --git a/client/src/components/LoginForm.tsx b/client/src/components/LoginForm.tsx index 8231c5f..ef8ba99 100644 --- a/client/src/components/LoginForm.tsx +++ b/client/src/components/LoginForm.tsx @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-misused-promises */ -import {FC, useContext} from 'react' +import {FC, useEffect} from 'react' import { useForm, SubmitHandler } from "react-hook-form"; -import { Context } from '../main'; import Logo from '../assets/logo.png' import { Link, useNavigate } from 'react-router-dom'; +import { useAppSelector, useAppDispatch } from '../hooks/redux'; +import { login } from '../store/reducers/UserSlice'; type Form = { email: string, @@ -11,16 +12,21 @@ type Form = { }; const LoginForm: FC = () => { - const navigate = useNavigate(); - const {store} = useContext(Context); + const dispatch = useAppDispatch(); + const {isAuth} = useAppSelector(state => state.UserSlice); + const { register, handleSubmit, formState: {isSubmitSuccessful} } = useForm
(); - const { register, handleSubmit } = useForm(); + useEffect(() => { + if(isSubmitSuccessful) { + if(isAuth) { + navigate('/'); + } + } + }, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps + const onSubmit: SubmitHandler = async (data) => { - await store.login(data.email, data.password); - if(store.isAuth) { - navigate('/'); - } + await dispatch(login({email: data.email, password: data.password})); } return ( @@ -39,7 +45,7 @@ const LoginForm: FC = () => {

Dont have account? Sign up here.

-
+ ) } diff --git a/client/src/components/RegisterForm.tsx b/client/src/components/RegisterForm.tsx index fca3a36..e314b02 100644 --- a/client/src/components/RegisterForm.tsx +++ b/client/src/components/RegisterForm.tsx @@ -1,9 +1,10 @@ /* eslint-disable @typescript-eslint/no-misused-promises */ -import {FC, useContext} from 'react' +import {FC, useEffect} from 'react' import { useForm, SubmitHandler } from "react-hook-form"; import { useNavigate, Link } from 'react-router-dom'; -import { Context } from '../main'; import Logo from '../assets/logo.png' +import { useAppSelector, useAppDispatch } from '../hooks/redux'; +import { registration } from '../store/reducers/UserSlice'; const RegisterForm: FC = () => { @@ -16,14 +17,20 @@ const RegisterForm: FC = () => { }; const navigate = useNavigate(); - const {store} = useContext(Context); + const dispatch = useAppDispatch(); + const {isAuth} = useAppSelector(state => state.UserSlice); + const { register, handleSubmit, watch, formState: {isSubmitSuccessful} } = useForm
(); - const { register, handleSubmit, watch } = useForm(); - const onSubmit: SubmitHandler = async (data) => { - await store.registration(data.email, data.name, data.sname, data.password); - if(store.isAuth) { - navigate('/'); + useEffect(() => { + if(isSubmitSuccessful) { + if(isAuth) { + navigate('/'); + } } + }, [isSubmitSuccessful]) // eslint-disable-line react-hooks/exhaustive-deps + + const onSubmit: SubmitHandler = async (data) => { + await dispatch(registration({email: data.email, name: data.name, sname: data.sname, password: data.password})); } return ( diff --git a/client/src/hooks/redux.ts b/client/src/hooks/redux.ts new file mode 100644 index 0000000..1770cf7 --- /dev/null +++ b/client/src/hooks/redux.ts @@ -0,0 +1,5 @@ +import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux"; +import { AppDispatch, RootState } from "../store/store"; + +export const useAppDispatch = () => useDispatch(); +export const useAppSelector: TypedUseSelectorHook = useSelector; \ No newline at end of file diff --git a/client/src/main.tsx b/client/src/main.tsx index ebd1618..164828e 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,22 +1,16 @@ -import React, { createContext } from 'react' +import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' -import Store from './store/store.ts' +import { store } from './store/store.ts' +import { Provider } from 'react-redux' -interface State { - store: Store -} - -const store = new Store(); - -export const Context = createContext({store}); ReactDOM.createRoot(document.getElementById('root')!).render( - + - + , ) diff --git a/client/src/models/response/ServerError.ts b/client/src/models/response/ServerError.ts new file mode 100644 index 0000000..32e2a5a --- /dev/null +++ b/client/src/models/response/ServerError.ts @@ -0,0 +1,3 @@ +export interface ServerError { + error: string +} \ No newline at end of file diff --git a/client/src/store/reducers/UserSlice.ts b/client/src/store/reducers/UserSlice.ts new file mode 100644 index 0000000..ae7674e --- /dev/null +++ b/client/src/store/reducers/UserSlice.ts @@ -0,0 +1,140 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit" +import { User } from "../../models/User" +import { AuthResponse } from "../../models/response/AuthResponse"; +import AuthService from "../../services/AuthService"; +import { ServerError } from "../../models/response/ServerError"; +import { AxiosError } from 'axios'; +import { API_URL } from "../../http"; +import axios from "axios"; + + + +export interface UserState { + user: User; + isAuth: boolean; + isLoading: boolean; +} + + + +const initialState: UserState = { + user: { + _id: '', + email: '', + name: '', + sname: '' + }, + isAuth: false, + isLoading: true +} + +export const registration = createAsyncThunk( + 'userSlice/registration', + async (user, {rejectWithValue}) => { + try { + const {email, name, sname, password} = user; + const response = await AuthService.registration(email, name, sname, password); + localStorage.setItem('token', response.data.accessToken); + return response.data.user; + } catch (error) { + const err = error as AxiosError; + const e = err.response?.data as ServerError + return rejectWithValue(e.error); + } + + } +) + +export const login = createAsyncThunk( + 'userSlice/login', + async (user, {rejectWithValue}) => { + try { + const {email, password} = user; + const response = await AuthService.login(email, password); + localStorage.setItem('token', response.data.accessToken); + return response.data.user; + } catch ( error ) { + const err = error as AxiosError; + const e = err.response?.data as ServerError + return rejectWithValue(e.error); + } + } +) + +export const checkAuth = createAsyncThunk( + 'userSlice/checkAuth', + async (_, {rejectWithValue}) => { + try { + const response = await axios.get(`${API_URL}/auth/refresh`, {withCredentials: true}); + localStorage.setItem('token', response.data.accessToken); + return response.data.user; + } catch (error) { + const err = error as AxiosError; + const e = err.response?.data as ServerError + return rejectWithValue(e.error); + } + } +) + +export const logout = createAsyncThunk( + 'userSlice/logout', + async (_, {rejectWithValue}) => { + try { + await AuthService.logout(); + localStorage.removeItem('token'); + return true; + } catch (error) { + const err = error as AxiosError; + const e = err.response?.data as ServerError + return rejectWithValue(e.error); + } + } +) + +export const userSlice = createSlice({ + name: 'userSlice', + initialState, + reducers: { + storeLoad(state) { + state.isLoading = false; + } + }, + extraReducers: (builder) => { + builder + .addCase(registration.fulfilled, (state, { payload }) => { + state.isAuth = true; + state.user = payload; + }) + .addCase(registration.rejected, (_, { payload }) => { + console.log(payload); + }) + .addCase(login.fulfilled, (state, { payload }) => { + state.isAuth = true; + state.user = payload; + }) + .addCase(login.rejected, (_, { payload }) => { + console.log(payload); + }) + .addCase(checkAuth.pending, (state) => { + state.isLoading = true; + }) + .addCase(checkAuth.fulfilled, (state, action) => { + state.user = action.payload; + state.isAuth = true; + state.isLoading = false; + }) + .addCase(checkAuth.rejected, (state, { payload }) => { + state.isLoading = false; + console.log(payload); + }) + .addCase(logout.fulfilled, (state) => { + state.isAuth = false; + state.user = {} as User; + }) + .addCase(logout.rejected, (_, { payload }) => { + console.log(payload); + }) + } +}) + +export default userSlice.reducer; \ No newline at end of file diff --git a/client/src/store/store.ts b/client/src/store/store.ts index a282a1d..fad01d0 100644 --- a/client/src/store/store.ts +++ b/client/src/store/store.ts @@ -1,79 +1,16 @@ -import { User } from "../models/User" -import { makeAutoObservable } from "mobx"; -import AuthService from "../services/AuthService"; -import axios from "axios"; -import { AuthResponse } from "../models/response/AuthResponse"; -import { API_URL } from "../http"; - -export default class Store { - user = {} as User; - isAuth = false; - isLoading = true; - - constructor() { - makeAutoObservable(this); - } - - setAuth(bool: boolean) { - this.isAuth = bool; - } - - setUser(user: User) { - this.user = user; - } - - setLoading(bool: boolean) { - this.isLoading = bool; - } - - async registration (email: string, name: string, sname: string, password: string) { - try { - const response = await AuthService.registration(email, name, sname, password); - localStorage.setItem('token', response.data.accessToken); - this.setAuth(true); - this.setUser(response.data.user) - } catch (e) { - console.log(e) - } - } - - async login (email: string, password: string) { - try { - const response = await AuthService.login(email, password); - localStorage.setItem('token', response.data.accessToken); - this.setAuth(true); - this.setUser(response.data.user) - } catch (e) { - console.log(e) - } - } - - async logout () { - try { - await AuthService.logout(); - localStorage.removeItem('token'); - this.setAuth(false); - this.setUser({} as User); - } catch (e) { - console.log(e) - } - } - - async checkAuth() { - this.setLoading(true); - try { - const response = await axios.get(`${API_URL}/auth/refresh`, {withCredentials: true}); - localStorage.setItem('token', response.data.accessToken); - this.setAuth(true); - this.setUser(response.data.user) - } catch (e) { - console.log(e); - } finally { - this.setLoading(false); - } - } - - storeLoad() { - this.setLoading(false); - } -} \ No newline at end of file +import { combineReducers, configureStore } from "@reduxjs/toolkit"; +import UserSlice from "./reducers/UserSlice"; + +const rootReducer = combineReducers({ + UserSlice +}) + +export const store = () => { + return configureStore({ + reducer: rootReducer + }) +} + +export type RootState = ReturnType +export type AppStore = ReturnType +export type AppDispatch = AppStore['dispatch'] \ No newline at end of file