forked from VinokurovVE/tests
Auth: SignIn, SignUp (TODO: rewrite into react-hook-form)
This commit is contained in:
41
frontend_reactjs/package-lock.json
generated
41
frontend_reactjs/package-lock.json
generated
@ -13,12 +13,13 @@
|
|||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
|
"@mui/x-data-grid": "^7.7.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^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",
|
"react-router-dom": "^6.23.1",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zustand": "^4.5.2"
|
"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": {
|
"node_modules/@nodelib/fs.scandir": {
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
@ -6753,9 +6779,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-hook-form": {
|
"node_modules/react-hook-form": {
|
||||||
"version": "7.51.5",
|
"version": "7.52.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz",
|
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz",
|
||||||
"integrity": "sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==",
|
"integrity": "sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.22.0"
|
"node": ">=12.22.0"
|
||||||
},
|
},
|
||||||
@ -6764,7 +6790,7 @@
|
|||||||
"url": "https://opencollective.com/react-hook-form"
|
"url": "https://opencollective.com/react-hook-form"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^16.8.0 || ^17 || ^18"
|
"react": "^16.8.0 || ^17 || ^18 || ^19"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-is": {
|
"node_modules/react-is": {
|
||||||
@ -6935,6 +6961,11 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
@ -15,12 +15,13 @@
|
|||||||
"@emotion/styled": "^11.11.5",
|
"@emotion/styled": "^11.11.5",
|
||||||
"@mui/icons-material": "^5.15.20",
|
"@mui/icons-material": "^5.15.20",
|
||||||
"@mui/material": "^5.15.20",
|
"@mui/material": "^5.15.20",
|
||||||
|
"@mui/x-data-grid": "^7.7.1",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"axios": "^1.7.2",
|
"axios": "^1.7.2",
|
||||||
"postcss": "^8.4.38",
|
"postcss": "^8.4.38",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^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",
|
"react-router-dom": "^6.23.1",
|
||||||
"zod": "^3.23.8",
|
"zod": "^3.23.8",
|
||||||
"zustand": "^4.5.2"
|
"zustand": "^4.5.2"
|
||||||
|
@ -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 Main from "./pages/Main"
|
||||||
import Users from "./pages/Users"
|
import Users from "./pages/Users"
|
||||||
import Roles from "./pages/Roles"
|
import Roles from "./pages/Roles"
|
||||||
@ -7,17 +7,41 @@ import DashboardLayout from "./layouts/DashboardLayout"
|
|||||||
import MainLayout from "./layouts/MainLayout"
|
import MainLayout from "./layouts/MainLayout"
|
||||||
import SignIn from "./pages/auth/SignIn"
|
import SignIn from "./pages/auth/SignIn"
|
||||||
import ApiTest from "./pages/ApiTest"
|
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() {
|
function App() {
|
||||||
|
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 (
|
||||||
|
<CircularProgress />
|
||||||
|
)
|
||||||
|
} else {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Router>
|
<Router>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<MainLayout/>}>
|
<Route element={<MainLayout />}>
|
||||||
<Route path="/auth/signin" element={<SignIn/>}/>
|
<Route path="/auth/signin" element={<SignIn />} />
|
||||||
|
<Route path="/auth/signup" element={<SignUp />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route element={<DashboardLayout />}>
|
<Route element={auth.isAuthenticated ? <DashboardLayout /> : <Navigate to={"/auth/signin"} />}>
|
||||||
<Route path="/" element={<Main />} />
|
<Route path="/" element={<Main />} />
|
||||||
<Route path="/user" element={<Users />} />
|
<Route path="/user" element={<Users />} />
|
||||||
<Route path="/role" element={<Roles />} />
|
<Route path="/role" element={<Roles />} />
|
||||||
@ -28,6 +52,7 @@ function App() {
|
|||||||
</Router>
|
</Router>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App
|
export default App
|
24
frontend_reactjs/src/components/DataTable.tsx
Normal file
24
frontend_reactjs/src/components/DataTable.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { DataGrid } from '@mui/x-data-grid';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
rows: any,
|
||||||
|
columns: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DataTable(props: Props) {
|
||||||
|
return (
|
||||||
|
<div style={{ flexGrow: 1, height: "100%", width: '100%' }}>
|
||||||
|
<DataGrid
|
||||||
|
rows={props.rows}
|
||||||
|
columns={props.columns}
|
||||||
|
initialState={{
|
||||||
|
pagination: {
|
||||||
|
paginationModel: { page: 0, pageSize: 5 },
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
pageSizeOptions={[10, 20, 50, 100]}
|
||||||
|
checkboxSelection
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
import { useState, useEffect, useMemo } from 'react';
|
import { useState, useEffect, useMemo } from 'react';
|
||||||
|
import axiosInstance from '../http/axiosInstance';
|
||||||
export function useDataFetching<T>(url: string, initData: T): T {
|
export function useDataFetching<T>(url: string, initData: T): T {
|
||||||
const [data, setData] = useState<T>(initData);
|
const [data, setData] = useState<T>(initData);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
const response = await fetch(url);
|
const response = await axiosInstance.get(url);
|
||||||
const result = await response.json();
|
const result = await response.data;
|
||||||
setData(result);
|
setData(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
19
frontend_reactjs/src/components/UserData.ts
Normal file
19
frontend_reactjs/src/components/UserData.ts
Normal file
@ -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<T>(token: string, initData: T): T {
|
||||||
|
const [userData, setUserData] = useState<T>(initData)
|
||||||
|
|
||||||
|
useEffect(()=> {
|
||||||
|
const fetchUserData = async (token: string) => {
|
||||||
|
const response = await AuthService.getCurrentUser(token)
|
||||||
|
setUserData(response.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchUserData(token)
|
||||||
|
}, [token])
|
||||||
|
|
||||||
|
const memoizedData = useMemo<T>(() => userData, [userData])
|
||||||
|
return memoizedData
|
||||||
|
}
|
21
frontend_reactjs/src/http/axiosInstance.ts
Normal file
21
frontend_reactjs/src/http/axiosInstance.ts
Normal file
@ -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;
|
@ -1,6 +1,6 @@
|
|||||||
// Layout for dashboard with responsive drawer
|
// 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 * as React from 'react';
|
||||||
import AppBar from '@mui/material/AppBar';
|
import AppBar from '@mui/material/AppBar';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
@ -17,10 +17,14 @@ import MenuIcon from '@mui/icons-material/Menu';
|
|||||||
import Toolbar from '@mui/material/Toolbar';
|
import Toolbar from '@mui/material/Toolbar';
|
||||||
import Typography from '@mui/material/Typography';
|
import Typography from '@mui/material/Typography';
|
||||||
import { Api, ExitToApp, Home, People, Settings, Shield } from "@mui/icons-material";
|
import { Api, ExitToApp, Home, People, Settings, Shield } from "@mui/icons-material";
|
||||||
|
import { UserData, useAuthStore } from "../store/auth";
|
||||||
|
|
||||||
const drawerWidth = 240;
|
const drawerWidth = 240;
|
||||||
|
|
||||||
export default function DashboardLayout() {
|
export default function DashboardLayout() {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const [userData, setUserData] = React.useState<UserData>();
|
||||||
|
|
||||||
//const { window } = props;
|
//const { window } = props;
|
||||||
const [mobileOpen, setMobileOpen] = React.useState(false);
|
const [mobileOpen, setMobileOpen] = React.useState(false);
|
||||||
const [isClosing, setIsClosing] = React.useState(false);
|
const [isClosing, setIsClosing] = React.useState(false);
|
||||||
@ -78,7 +82,13 @@ export default function DashboardLayout() {
|
|||||||
|
|
||||||
const drawer = (
|
const drawer = (
|
||||||
<div>
|
<div>
|
||||||
<Toolbar />
|
<Toolbar>
|
||||||
|
<Box>
|
||||||
|
<Typography>{userData?.name} {userData?.surname}</Typography>
|
||||||
|
<Divider />
|
||||||
|
<Typography variant="caption">{userData?.login}</Typography>
|
||||||
|
</Box>
|
||||||
|
</Toolbar>
|
||||||
|
|
||||||
<Divider />
|
<Divider />
|
||||||
|
|
||||||
@ -112,8 +122,21 @@ export default function DashboardLayout() {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (authStore) {
|
||||||
|
const stored = authStore.getUserData()
|
||||||
|
if (stored) {
|
||||||
|
setUserData(stored)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [authStore])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex' }}>
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexGrow: 1,
|
||||||
|
height: "100vh"
|
||||||
|
}}>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<AppBar
|
<AppBar
|
||||||
position="fixed"
|
position="fixed"
|
||||||
@ -173,7 +196,11 @@ export default function DashboardLayout() {
|
|||||||
|
|
||||||
<Box
|
<Box
|
||||||
component="main"
|
component="main"
|
||||||
sx={{ flexGrow: 1, p: 3, width: { sm: `calc(100% - ${drawerWidth}px)` } }}
|
sx={{
|
||||||
|
flexGrow: 1,
|
||||||
|
p: 3,
|
||||||
|
width: { sm: `calc(100% - ${drawerWidth}px)` }
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Toolbar />
|
<Toolbar />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
@ -3,6 +3,18 @@ import ReactDOM from 'react-dom/client'
|
|||||||
import App from './App.tsx'
|
import App from './App.tsx'
|
||||||
import './index.css'
|
import './index.css'
|
||||||
import { registerSW } from 'virtual:pwa-register'
|
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({
|
const updateSW = registerSW({
|
||||||
onNeedRefresh() {
|
onNeedRefresh() {
|
||||||
@ -17,6 +29,8 @@ const updateSW = registerSW({
|
|||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
<ThemeProvider theme={theme}>
|
||||||
<App />
|
<App />
|
||||||
|
</ThemeProvider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
)
|
)
|
||||||
|
@ -2,22 +2,43 @@ import { useEffect, useState } from "react"
|
|||||||
import UserService from "../services/UserService"
|
import UserService from "../services/UserService"
|
||||||
import AuthService from "../services/AuthService"
|
import AuthService from "../services/AuthService"
|
||||||
import { Button } from "@mui/material"
|
import { Button } from "@mui/material"
|
||||||
|
import DataTable from "../components/DataTable"
|
||||||
|
import { GridColDef } from "@mui/x-data-grid"
|
||||||
|
|
||||||
export default function ApiTest() {
|
export default function ApiTest() {
|
||||||
const [temp, setTemp] = useState<any>(null)
|
const [users, setUsers] = useState<any>(null)
|
||||||
|
|
||||||
const hello = async () => {
|
const getUsers = async () => {
|
||||||
await AuthService.hello().then(response => {
|
await AuthService.getUsers().then(response => {
|
||||||
setTemp(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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>{JSON.stringify(temp)}</div>
|
<Button onClick={() => getUsers()}>
|
||||||
<Button onClick={() => hello()}>
|
Get users
|
||||||
Hello
|
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
{users &&
|
||||||
|
<DataTable rows={users} columns={columns}/>
|
||||||
|
}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -2,6 +2,10 @@ import { useState } from 'react'
|
|||||||
import RoleCard from '../components/RoleCard'
|
import RoleCard from '../components/RoleCard'
|
||||||
import Modal from '../components/Modal'
|
import Modal from '../components/Modal'
|
||||||
import useDataFetching from '../components/FetchingData'
|
import useDataFetching from '../components/FetchingData'
|
||||||
|
import RoleService from '../services/RoleService'
|
||||||
|
import { Box, Button } from '@mui/material'
|
||||||
|
import DataTable from '../components/DataTable'
|
||||||
|
import { GridColDef } from '@mui/x-data-grid'
|
||||||
|
|
||||||
interface IRoleCard {
|
interface IRoleCard {
|
||||||
id: number
|
id: number
|
||||||
@ -11,12 +15,38 @@ interface Props {
|
|||||||
showModal: boolean;
|
showModal: boolean;
|
||||||
}
|
}
|
||||||
function Roles() {
|
function Roles() {
|
||||||
|
const [roles, setRoles] = useState<any>(null)
|
||||||
|
|
||||||
|
const getRoles = async () => {
|
||||||
|
await RoleService.getRoles().then(response => {
|
||||||
|
setRoles(response.data)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const [showModal, setShowModal] = useState<Props>({ showModal: false });
|
const [showModal, setShowModal] = useState<Props>({ showModal: false });
|
||||||
const cards = useDataFetching<IRoleCard[]>(`${import.meta.env.VITE_API_URL}/auth/role/`, [])
|
const cards = useDataFetching<IRoleCard[]>(`${import.meta.env.VITE_API_AUTH_URL}/auth/roles/`, [])
|
||||||
|
|
||||||
|
const columns: GridColDef[] = [
|
||||||
|
{ field: 'id', headerName: 'ID', type: "number", width: 70 },
|
||||||
|
{ field: 'name', headerName: 'Название', width: 90 },
|
||||||
|
{ field: 'description', headerName: 'Описание', width: 90 },
|
||||||
|
];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box>
|
||||||
|
<Button onClick={() => getRoles()}>
|
||||||
|
Get roles
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{roles &&
|
||||||
|
<DataTable rows={roles} columns={columns} />
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{cards.map((card, index) => <RoleCard key={index} {...card} />)}
|
{cards.length > 0 && cards.map((card, index) => <RoleCard key={index} {...card} />)}
|
||||||
<button className='absolute w-0 h-0' onClick={() => setShowModal({ showModal: true })}>+</button>
|
<button className='absolute w-0 h-0' onClick={() => setShowModal({ showModal: true })}>+</button>
|
||||||
<Modal {...showModal} />
|
<Modal {...showModal} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,43 @@
|
|||||||
import Card from '../components/Card'
|
import { useEffect, useState } from "react"
|
||||||
import useDataFetching from '../components/FetchingData'
|
import AuthService from "../services/AuthService"
|
||||||
interface ICard {
|
import { Box, Button } from "@mui/material"
|
||||||
firstname: string
|
import DataTable from "../components/DataTable"
|
||||||
lastname: string
|
import { GridColDef } from "@mui/x-data-grid"
|
||||||
email: string
|
|
||||||
}
|
export default function Users() {
|
||||||
|
const [users, setUsers] = useState<any>(null)
|
||||||
|
|
||||||
|
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
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function Users() {
|
|
||||||
const cards = useDataFetching<ICard[]>(`${import.meta.env.VITE_API_URL}/auth/user/`, [])
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<Box>
|
||||||
{cards.map((card, index) => <Card key={index} {...card} />)}
|
<Button onClick={() => getUsers()}>
|
||||||
</div>
|
Get users
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{users &&
|
||||||
|
<DataTable rows={users} columns={columns}/>
|
||||||
|
}
|
||||||
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Users
|
|
@ -1,9 +1,99 @@
|
|||||||
import React from 'react'
|
import React, { useState, ChangeEvent, FormEvent } from 'react';
|
||||||
|
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||||
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import { SignInFormData, ApiResponse } from '../../types/auth';
|
||||||
|
import { UserData, useAuthStore } from '../../store/auth';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import axiosInstance from '../../http/axiosInstance';
|
||||||
|
import AuthService from '../../services/AuthService';
|
||||||
|
|
||||||
const SignIn = () => {
|
const SignIn = () => {
|
||||||
return (
|
const [formData, setFormData] = useState<SignInFormData>({
|
||||||
<div>SignIn</div>
|
username: '',
|
||||||
)
|
password: '',
|
||||||
}
|
grant_type: 'password',
|
||||||
|
scope: '',
|
||||||
|
client_id: '',
|
||||||
|
client_secret: ''
|
||||||
|
});
|
||||||
|
|
||||||
export default SignIn
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formBody = new URLSearchParams();
|
||||||
|
for (const key in formData) {
|
||||||
|
formBody.append(key, formData[key as keyof SignInFormData] as string);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response: AxiosResponse<ApiResponse> = await axiosInstance.post(`/auth/login`, formBody, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
console.log('Вход произошел успешно:', response.data);
|
||||||
|
const token = response.data.access_token
|
||||||
|
|
||||||
|
const userDataResponse: AxiosResponse<ApiResponse> = await AuthService.getCurrentUser(token)
|
||||||
|
|
||||||
|
console.log('Пользователь:', userDataResponse.data)
|
||||||
|
|
||||||
|
authStore.setUserData(JSON.stringify(userDataResponse.data))
|
||||||
|
|
||||||
|
authStore.login(token)
|
||||||
|
|
||||||
|
navigate('/');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при входе:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Box my={4}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Вход
|
||||||
|
</Typography>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="username"
|
||||||
|
label="Логин"
|
||||||
|
value={formData.username}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
label="Пароль"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button type="submit" variant="contained" color="primary">
|
||||||
|
Вход
|
||||||
|
</Button>
|
||||||
|
<Button href="/auth/signup" type="button" variant="text" color="primary">
|
||||||
|
Регистрация
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignIn;
|
@ -0,0 +1,105 @@
|
|||||||
|
// src/components/SignUp.tsx
|
||||||
|
|
||||||
|
import React, { useState, ChangeEvent, FormEvent } from 'react';
|
||||||
|
import { TextField, Button, Container, Typography, Box } from '@mui/material';
|
||||||
|
import axios, { AxiosResponse } from 'axios';
|
||||||
|
import { SignUpFormData, ApiResponse } from '../../types/auth';
|
||||||
|
import axiosInstance from '../../http/axiosInstance';
|
||||||
|
|
||||||
|
const SignUp: React.FC = () => {
|
||||||
|
const [formData, setFormData] = useState<SignUpFormData>({
|
||||||
|
email: '',
|
||||||
|
login: '',
|
||||||
|
phone: '',
|
||||||
|
name: '',
|
||||||
|
surname: '',
|
||||||
|
is_active: true,
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[e.target.name]: e.target.value,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = async (e: FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
try {
|
||||||
|
const response: AxiosResponse<ApiResponse> = await axiosInstance.post(`${import.meta.env.VITE_API_AUTH_URL}/auth/user`, formData);
|
||||||
|
console.log('Успешная регистрация:', response.data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка регистрации:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container maxWidth="sm">
|
||||||
|
<Box my={4}>
|
||||||
|
<Typography variant="h4" component="h1" gutterBottom>
|
||||||
|
Регистрация
|
||||||
|
</Typography>
|
||||||
|
<form onSubmit={handleSubmit}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="email"
|
||||||
|
label="Email"
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="login"
|
||||||
|
label="Логин"
|
||||||
|
value={formData.login}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="phone"
|
||||||
|
label="Телефон"
|
||||||
|
value={formData.phone}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="name"
|
||||||
|
label="Имя"
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
name="surname"
|
||||||
|
label="Фамилия"
|
||||||
|
value={formData.surname}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
label="Пароль"
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<Button type="submit" variant="contained" color="primary">
|
||||||
|
Зарегистрироваться
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SignUp;
|
@ -1,7 +1,16 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import axiosInstance from "../http/axiosInstance";
|
||||||
|
|
||||||
export default class AuthService {
|
export default class AuthService {
|
||||||
static async hello() {
|
static async hello() {
|
||||||
return await axios.get(`${import.meta.env.VITE_API_AUTH_URL}/hello`)
|
return await axios.get(`${import.meta.env.VITE_API_AUTH_URL}/hello`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async getUsers() {
|
||||||
|
return await axiosInstance.get(`/auth/user`)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getCurrentUser(token: string){
|
||||||
|
return await axiosInstance.get(`/auth/get_current_user/${token}`)
|
||||||
|
}
|
||||||
}
|
}
|
11
frontend_reactjs/src/services/RoleService.ts
Normal file
11
frontend_reactjs/src/services/RoleService.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import axiosInstance from "../http/axiosInstance";
|
||||||
|
|
||||||
|
export default class RoleService {
|
||||||
|
static async getRoles() {
|
||||||
|
return await axiosInstance.get(`/auth/roles`)
|
||||||
|
}
|
||||||
|
|
||||||
|
static async getRoleById(id: number) {
|
||||||
|
return await axiosInstance.get(`/auth/roles/${id}`)
|
||||||
|
}
|
||||||
|
}
|
50
frontend_reactjs/src/store/auth.ts
Normal file
50
frontend_reactjs/src/store/auth.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
export interface UserData {
|
||||||
|
id: number;
|
||||||
|
email: string;
|
||||||
|
login: string;
|
||||||
|
phone: string;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
is_active: boolean;
|
||||||
|
role_id: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AuthState {
|
||||||
|
isAuthenticated: boolean;
|
||||||
|
token: string | null;
|
||||||
|
login: (token: string) => void;
|
||||||
|
logout: () => void;
|
||||||
|
initializeAuth: () => void;
|
||||||
|
userData: UserData | {};
|
||||||
|
getUserData: () => UserData;
|
||||||
|
setUserData: (userData: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthStore = create<AuthState>((set) => ({
|
||||||
|
isAuthenticated: false,
|
||||||
|
token: null,
|
||||||
|
login: (token: string) => {
|
||||||
|
localStorage.setItem('authToken', token);
|
||||||
|
set({ isAuthenticated: true, token });
|
||||||
|
},
|
||||||
|
logout: () => {
|
||||||
|
localStorage.removeItem('authToken');
|
||||||
|
set({ isAuthenticated: false, token: null });
|
||||||
|
},
|
||||||
|
initializeAuth: () => {
|
||||||
|
const token = localStorage.getItem('authToken');
|
||||||
|
if (token) {
|
||||||
|
set({ isAuthenticated: true, token });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userData: {},
|
||||||
|
setUserData: (userData: string) => {
|
||||||
|
localStorage.setItem('userData', userData)
|
||||||
|
},
|
||||||
|
getUserData: () => {
|
||||||
|
const userData = localStorage.getItem('userData')
|
||||||
|
return JSON.parse(userData || "")
|
||||||
|
}
|
||||||
|
}));
|
25
frontend_reactjs/src/types/auth.ts
Normal file
25
frontend_reactjs/src/types/auth.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
export interface SignUpFormData {
|
||||||
|
email: string;
|
||||||
|
login: string;
|
||||||
|
phone: string;
|
||||||
|
name: string;
|
||||||
|
surname: string;
|
||||||
|
is_active: boolean;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SignInFormData {
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
grant_type: string;
|
||||||
|
scope?: string;
|
||||||
|
client_id?: string;
|
||||||
|
client_secret?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse {
|
||||||
|
access_token: any;
|
||||||
|
data: any;
|
||||||
|
status: number;
|
||||||
|
statusText: string;
|
||||||
|
}
|
@ -936,7 +936,7 @@
|
|||||||
resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
|
resolved "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz"
|
||||||
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
|
||||||
|
|
||||||
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
"@babel/runtime@^7.11.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.24.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
|
||||||
version "7.24.7"
|
version "7.24.7"
|
||||||
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz"
|
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz"
|
||||||
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
|
integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==
|
||||||
@ -1244,7 +1244,7 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@babel/runtime" "^7.23.9"
|
"@babel/runtime" "^7.23.9"
|
||||||
|
|
||||||
"@mui/material@^5.0.0", "@mui/material@^5.15.20":
|
"@mui/material@^5.0.0", "@mui/material@^5.15.14", "@mui/material@^5.15.20":
|
||||||
version "5.15.20"
|
version "5.15.20"
|
||||||
resolved "https://registry.npmjs.org/@mui/material/-/material-5.15.20.tgz"
|
resolved "https://registry.npmjs.org/@mui/material/-/material-5.15.20.tgz"
|
||||||
integrity sha512-tVq3l4qoXx/NxUgIx/x3lZiPn/5xDbdTE8VrLczNpfblLYZzlrbxA7kb9mI8NoBF6+w9WE9IrxWnKK5KlPI2bg==
|
integrity sha512-tVq3l4qoXx/NxUgIx/x3lZiPn/5xDbdTE8VrLczNpfblLYZzlrbxA7kb9mI8NoBF6+w9WE9IrxWnKK5KlPI2bg==
|
||||||
@ -1310,6 +1310,18 @@
|
|||||||
prop-types "^15.8.1"
|
prop-types "^15.8.1"
|
||||||
react-is "^18.2.0"
|
react-is "^18.2.0"
|
||||||
|
|
||||||
|
"@mui/x-data-grid@^7.7.1":
|
||||||
|
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"
|
||||||
|
|
||||||
"@nodelib/fs.scandir@2.1.5":
|
"@nodelib/fs.scandir@2.1.5":
|
||||||
version "2.1.5"
|
version "2.1.5"
|
||||||
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz"
|
||||||
@ -1892,7 +1904,7 @@ chokidar@^3.5.3:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
clsx@^2.1.0:
|
clsx@^2.1.0, clsx@^2.1.1:
|
||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
resolved "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz"
|
||||||
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==
|
||||||
@ -3459,10 +3471,10 @@ randombytes@^2.1.0:
|
|||||||
loose-envify "^1.1.0"
|
loose-envify "^1.1.0"
|
||||||
scheduler "^0.23.2"
|
scheduler "^0.23.2"
|
||||||
|
|
||||||
react-hook-form@^7.51.5:
|
react-hook-form@^7.52.0:
|
||||||
version "7.51.5"
|
version "7.52.0"
|
||||||
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.5.tgz"
|
resolved "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz"
|
||||||
integrity sha512-J2ILT5gWx1XUIJRETiA7M19iXHlG74+6O3KApzvqB/w8S5NQR7AbU8HVZrMALdmDgWpRPYiZJl0zx8Z4L2mP6Q==
|
integrity sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==
|
||||||
|
|
||||||
react-is@^16.13.1:
|
react-is@^16.13.1:
|
||||||
version "16.13.1"
|
version "16.13.1"
|
||||||
@ -3504,7 +3516,7 @@ react-transition-group@^4.4.5:
|
|||||||
loose-envify "^1.4.0"
|
loose-envify "^1.4.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
"react@^16.8.0 || ^17 || ^18", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
|
"react@^16.8.0 || ^17 || ^18 || ^19", "react@^16.8.0 || ^17.0.0 || ^18.0.0", "react@^17.0.0 || ^18.0.0", react@^18.2.0, react@^18.3.1, react@>=16.6.0, react@>=16.8, react@>=16.8.0:
|
||||||
version "18.3.1"
|
version "18.3.1"
|
||||||
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
resolved "https://registry.npmjs.org/react/-/react-18.3.1.tgz"
|
||||||
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
integrity sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==
|
||||||
@ -3583,6 +3595,11 @@ require-from-string@^2.0.2:
|
|||||||
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
|
resolved "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz"
|
||||||
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
|
||||||
|
|
||||||
|
reselect@^4.1.8:
|
||||||
|
version "4.1.8"
|
||||||
|
resolved "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz"
|
||||||
|
integrity sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==
|
||||||
|
|
||||||
resolve-from@^4.0.0:
|
resolve-from@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||||
|
Reference in New Issue
Block a user