diff --git a/frontend_reactjs/package-lock.json b/frontend_reactjs/package-lock.json
index 81d1a46..9847a4a 100644
--- a/frontend_reactjs/package-lock.json
+++ b/frontend_reactjs/package-lock.json
@@ -13,12 +13,13 @@
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
+ "@mui/x-data-grid": "^7.7.1",
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-hook-form": "^7.51.5",
+ "react-hook-form": "^7.52.0",
"react-router-dom": "^6.23.1",
"zod": "^3.23.8",
"zustand": "^4.5.2"
@@ -2885,6 +2886,31 @@
}
}
},
+ "node_modules/@mui/x-data-grid": {
+ "version": "7.7.1",
+ "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.7.1.tgz",
+ "integrity": "sha512-5XsvuVpJfjV2ERtNiVRWL+0UUq5rh2Tq8aLZdJ8Ca5PnweEfNzOesQMlf0lpjXqnzuoq7uTwvICqoAMjsTTglg==",
+ "dependencies": {
+ "@babel/runtime": "^7.24.7",
+ "@mui/system": "^5.15.20",
+ "@mui/utils": "^5.15.20",
+ "clsx": "^2.1.1",
+ "prop-types": "^15.8.1",
+ "reselect": "^4.1.8"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mui-org"
+ },
+ "peerDependencies": {
+ "@mui/material": "^5.15.14",
+ "react": "^17.0.0 || ^18.0.0",
+ "react-dom": "^17.0.0 || ^18.0.0"
+ }
+ },
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
@@ -6753,9 +6779,9 @@
}
},
"node_modules/react-hook-form": {
- "version": "7.51.5",
- "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
- "integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
+ "version": "7.52.0",
+ "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz",
+ "integrity": "sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==",
"engines": {
"node": ">=12.22.0"
},
@@ -6764,7 +6790,7 @@
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
- "react": "^16.8.0 || ^17 || ^18"
+ "react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": {
@@ -6935,6 +6961,11 @@
"node": ">=0.10.0"
}
},
+ "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.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
diff --git a/frontend_reactjs/package.json b/frontend_reactjs/package.json
index 0a4684f..382f586 100644
--- a/frontend_reactjs/package.json
+++ b/frontend_reactjs/package.json
@@ -15,12 +15,13 @@
"@emotion/styled": "^11.11.5",
"@mui/icons-material": "^5.15.20",
"@mui/material": "^5.15.20",
+ "@mui/x-data-grid": "^7.7.1",
"autoprefixer": "^10.4.19",
"axios": "^1.7.2",
"postcss": "^8.4.38",
"react": "^18.2.0",
"react-dom": "^18.2.0",
- "react-hook-form": "^7.51.5",
+ "react-hook-form": "^7.52.0",
"react-router-dom": "^6.23.1",
"zod": "^3.23.8",
"zustand": "^4.5.2"
diff --git a/frontend_reactjs/src/App.tsx b/frontend_reactjs/src/App.tsx
index 72a2a12..5958b75 100644
--- a/frontend_reactjs/src/App.tsx
+++ b/frontend_reactjs/src/App.tsx
@@ -1,4 +1,4 @@
-import { BrowserRouter as Router, Route, Routes } from "react-router-dom"
+import { BrowserRouter as Router, Route, Routes, Navigate, redirect } from "react-router-dom"
import Main from "./pages/Main"
import Users from "./pages/Users"
import Roles from "./pages/Roles"
@@ -7,27 +7,52 @@ import DashboardLayout from "./layouts/DashboardLayout"
import MainLayout from "./layouts/MainLayout"
import SignIn from "./pages/auth/SignIn"
import ApiTest from "./pages/ApiTest"
+import SignUp from "./pages/auth/SignUp"
+import { useAuthStore } from "./store/auth"
+import { useEffect, useState } from "react"
+import { CircularProgress } from "@mui/material"
function App() {
- return (
- <>
-
-
- }>
- }/>
-
+ const auth = useAuthStore()
+ const [isLoading, setIsLoading] = useState(true)
- }>
- } />
- } />
- } />
- } />
- } />
-
-
-
- >
- )
+ useEffect(() => {
+ auth.initializeAuth()
+ }, [])
+
+ // Once auth is there, set loading to false and render the app
+ useEffect(() => {
+ if (auth) {
+ setIsLoading(false)
+ }
+ }, [auth])
+
+ if (isLoading) {
+ return (
+
+ )
+ } else {
+ return (
+ <>
+
+
+ }>
+ } />
+ } />
+
+
+ : }>
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+ >
+ )
+ }
}
export default App
\ No newline at end of file
diff --git a/frontend_reactjs/src/components/DataTable.tsx b/frontend_reactjs/src/components/DataTable.tsx
new file mode 100644
index 0000000..c32b05e
--- /dev/null
+++ b/frontend_reactjs/src/components/DataTable.tsx
@@ -0,0 +1,24 @@
+import { DataGrid } from '@mui/x-data-grid';
+
+interface Props {
+ rows: any,
+ columns: any
+}
+
+export default function DataTable(props: Props) {
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend_reactjs/src/components/FetchingData.ts b/frontend_reactjs/src/components/FetchingData.ts
index fdaf04e..cf9c3ed 100644
--- a/frontend_reactjs/src/components/FetchingData.ts
+++ b/frontend_reactjs/src/components/FetchingData.ts
@@ -1,11 +1,12 @@
import { useState, useEffect, useMemo } from 'react';
+import axiosInstance from '../http/axiosInstance';
export function useDataFetching(url: string, initData: T): T {
const [data, setData] = useState(initData);
useEffect(() => {
const fetchData = async () => {
- const response = await fetch(url);
- const result = await response.json();
+ const response = await axiosInstance.get(url);
+ const result = await response.data;
setData(result);
};
diff --git a/frontend_reactjs/src/components/UserData.ts b/frontend_reactjs/src/components/UserData.ts
new file mode 100644
index 0000000..a83ff95
--- /dev/null
+++ b/frontend_reactjs/src/components/UserData.ts
@@ -0,0 +1,19 @@
+import { memo, useEffect, useMemo, useState } from "react";
+import UserService from "../services/UserService";
+import AuthService from "../services/AuthService";
+
+export default function useUserData(token: string, initData: T): T {
+ const [userData, setUserData] = useState(initData)
+
+ useEffect(()=> {
+ const fetchUserData = async (token: string) => {
+ const response = await AuthService.getCurrentUser(token)
+ setUserData(response.data)
+ }
+
+ fetchUserData(token)
+ }, [token])
+
+ const memoizedData = useMemo(() => userData, [userData])
+ return memoizedData
+}
\ No newline at end of file
diff --git a/frontend_reactjs/src/http/axiosInstance.ts b/frontend_reactjs/src/http/axiosInstance.ts
new file mode 100644
index 0000000..671dee2
--- /dev/null
+++ b/frontend_reactjs/src/http/axiosInstance.ts
@@ -0,0 +1,21 @@
+import axios from 'axios';
+import { useAuthStore } from '../store/auth';
+
+const axiosInstance = axios.create({
+ baseURL: `${import.meta.env.VITE_API_AUTH_URL}`,
+});
+
+axiosInstance.interceptors.request.use(
+ (config) => {
+ const token = useAuthStore.getState().token;
+ if (token) {
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+);
+
+export default axiosInstance;
diff --git a/frontend_reactjs/src/layouts/DashboardLayout.tsx b/frontend_reactjs/src/layouts/DashboardLayout.tsx
index d3c9923..0bb7296 100644
--- a/frontend_reactjs/src/layouts/DashboardLayout.tsx
+++ b/frontend_reactjs/src/layouts/DashboardLayout.tsx
@@ -1,6 +1,6 @@
// Layout for dashboard with responsive drawer
-import { Outlet } from "react-router-dom"
+import { Navigate, Outlet } from "react-router-dom"
import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
@@ -17,10 +17,14 @@ import MenuIcon from '@mui/icons-material/Menu';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import { Api, ExitToApp, Home, People, Settings, Shield } from "@mui/icons-material";
+import { UserData, useAuthStore } from "../store/auth";
const drawerWidth = 240;
export default function DashboardLayout() {
+ const authStore = useAuthStore();
+ const [userData, setUserData] = React.useState();
+
//const { window } = props;
const [mobileOpen, setMobileOpen] = React.useState(false);
const [isClosing, setIsClosing] = React.useState(false);
@@ -78,7 +82,13 @@ export default function DashboardLayout() {
const drawer = (
-
+
+
+ {userData?.name} {userData?.surname}
+
+ {userData?.login}
+
+
@@ -112,8 +122,21 @@ export default function DashboardLayout() {
);
+ React.useEffect(() => {
+ if (authStore) {
+ const stored = authStore.getUserData()
+ if (stored) {
+ setUserData(stored)
+ }
+ }
+ }, [authStore])
+
return (
-
+
diff --git a/frontend_reactjs/src/main.tsx b/frontend_reactjs/src/main.tsx
index 8782212..d66dc94 100644
--- a/frontend_reactjs/src/main.tsx
+++ b/frontend_reactjs/src/main.tsx
@@ -3,6 +3,18 @@ import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
import { registerSW } from 'virtual:pwa-register'
+import { ThemeProvider } from '@emotion/react'
+import { createTheme } from '@mui/material'
+import { ruRU } from '@mui/material/locale'
+
+const theme = createTheme(
+ {
+ palette: {
+ primary: { main: '#1976d2' },
+ },
+ },
+ ruRU,
+);
const updateSW = registerSW({
onNeedRefresh() {
@@ -17,6 +29,8 @@ const updateSW = registerSW({
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
-
+
+
+
,
)
diff --git a/frontend_reactjs/src/pages/ApiTest.tsx b/frontend_reactjs/src/pages/ApiTest.tsx
index 7ece3fc..10a1f6a 100644
--- a/frontend_reactjs/src/pages/ApiTest.tsx
+++ b/frontend_reactjs/src/pages/ApiTest.tsx
@@ -2,22 +2,43 @@ import { useEffect, useState } from "react"
import UserService from "../services/UserService"
import AuthService from "../services/AuthService"
import { Button } from "@mui/material"
+import DataTable from "../components/DataTable"
+import { GridColDef } from "@mui/x-data-grid"
export default function ApiTest() {
- const [temp, setTemp] = useState(null)
+ const [users, setUsers] = useState(null)
- const hello = async () => {
- await AuthService.hello().then(response => {
- setTemp(response)
+ const getUsers = async () => {
+ await AuthService.getUsers().then(response => {
+ setUsers(response.data)
})
}
+ const columns: GridColDef[] = [
+ { field: 'id', headerName: 'ID', type: "number", width: 70 },
+ { field: 'email', headerName: 'Email', width: 130 },
+ { field: 'login', headerName: 'Логин', width: 130 },
+ { field: 'phone', headerName: 'Телефон', width: 90 },
+ { field: 'name', headerName: 'Имя', width: 90 },
+ { field: 'surname', headerName: 'Фамилия', width: 90 },
+ { field: 'is_active', headerName: 'Активен', type: "boolean", width: 90 },
+ {
+ field: 'role_id',
+ headerName: 'Роль',
+ valueGetter: (value, row) => `${value}`,
+ width: 90
+ },
+ ];
+
return (
<>
- {JSON.stringify(temp)}
-