#4 create auth form

parent 2bd6873f
......@@ -22,7 +22,8 @@
"react-dom": "^18.2.0",
"react-redux": "^9.1.0",
"react-router-dom": "^6.22.3",
"redux-persist": "^6.0.0"
"redux-persist": "^6.0.0",
"sass": "^1.87.0"
},
"devDependencies": {
"@types/react": "^18.2.43",
......
......@@ -44,6 +44,9 @@ importers:
redux-persist:
specifier: ^6.0.0
version: 6.0.0(react@18.3.1)(redux@5.0.1)
sass:
specifier: ^1.87.0
version: 1.87.0
devDependencies:
'@types/react':
specifier: ^18.2.43
......@@ -62,7 +65,7 @@ importers:
version: 6.21.0(eslint@8.57.1)(typescript@5.2.2)
'@vitejs/plugin-react':
specifier: ^4.2.1
version: 4.4.1(vite@5.4.19)
version: 4.4.1(vite@5.4.19(sass@1.87.0))
eslint:
specifier: ^8.55.0
version: 8.57.1
......@@ -89,7 +92,7 @@ importers:
version: 5.2.2
vite:
specifier: ^5.0.8
version: 5.4.19
version: 5.4.19(sass@1.87.0)
packages:
......@@ -476,6 +479,88 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@parcel/watcher-android-arm64@2.5.1':
resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [android]
'@parcel/watcher-darwin-arm64@2.5.1':
resolution: {integrity: sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [darwin]
'@parcel/watcher-darwin-x64@2.5.1':
resolution: {integrity: sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [darwin]
'@parcel/watcher-freebsd-x64@2.5.1':
resolution: {integrity: sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [freebsd]
'@parcel/watcher-linux-arm-glibc@2.5.1':
resolution: {integrity: sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm-musl@2.5.1':
resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==}
engines: {node: '>= 10.0.0'}
cpu: [arm]
os: [linux]
'@parcel/watcher-linux-arm64-glibc@2.5.1':
resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-arm64-musl@2.5.1':
resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [linux]
'@parcel/watcher-linux-x64-glibc@2.5.1':
resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-linux-x64-musl@2.5.1':
resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [linux]
'@parcel/watcher-win32-arm64@2.5.1':
resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==}
engines: {node: '>= 10.0.0'}
cpu: [arm64]
os: [win32]
'@parcel/watcher-win32-ia32@2.5.1':
resolution: {integrity: sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==}
engines: {node: '>= 10.0.0'}
cpu: [ia32]
os: [win32]
'@parcel/watcher-win32-x64@2.5.1':
resolution: {integrity: sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==}
engines: {node: '>= 10.0.0'}
cpu: [x64]
os: [win32]
'@parcel/watcher@2.5.1':
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
engines: {node: '>= 10.0.0'}
'@pkgr/core@0.2.4':
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
......@@ -886,6 +971,10 @@ packages:
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
engines: {node: '>=10'}
chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'}
classnames@2.5.1:
resolution: {integrity: sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==}
......@@ -965,6 +1054,11 @@ packages:
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
engines: {node: '>=0.4.0'}
detect-libc@1.0.3:
resolution: {integrity: sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==}
engines: {node: '>=0.10'}
hasBin: true
dir-glob@3.0.1:
resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==}
engines: {node: '>=8'}
......@@ -1267,6 +1361,9 @@ packages:
immer@10.1.1:
resolution: {integrity: sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==}
immutable@5.1.1:
resolution: {integrity: sha512-3jatXi9ObIsPGr3N5hGw/vWWcTkq6hUYhpQz4k0wLC+owqWi/LiugIw9x0EdNZ2yGedKN/HzePiBvaJRXa0Ujg==}
import-fresh@3.3.1:
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
engines: {node: '>=6'}
......@@ -1497,6 +1594,9 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
node-addon-api@7.1.1:
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
node-releases@2.0.19:
resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==}
......@@ -1887,6 +1987,10 @@ packages:
resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==}
engines: {node: '>=0.10.0'}
readdirp@4.1.2:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
redux-persist@6.0.0:
resolution: {integrity: sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==}
peerDependencies:
......@@ -1963,6 +2067,11 @@ packages:
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
engines: {node: '>= 0.4'}
sass@1.87.0:
resolution: {integrity: sha512-d0NoFH4v6SjEK7BoX810Jsrhj7IQSYHAHLi/iSpgqKc7LaIDshFRlSg5LOymf9FqQhxEHs2W5ZQXlvy0KD45Uw==}
engines: {node: '>=14.0.0'}
hasBin: true
scheduler@0.23.2:
resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==}
......@@ -2606,6 +2715,67 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.19.1
'@parcel/watcher-android-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-arm64@2.5.1':
optional: true
'@parcel/watcher-darwin-x64@2.5.1':
optional: true
'@parcel/watcher-freebsd-x64@2.5.1':
optional: true
'@parcel/watcher-linux-arm-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm-musl@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-arm64-musl@2.5.1':
optional: true
'@parcel/watcher-linux-x64-glibc@2.5.1':
optional: true
'@parcel/watcher-linux-x64-musl@2.5.1':
optional: true
'@parcel/watcher-win32-arm64@2.5.1':
optional: true
'@parcel/watcher-win32-ia32@2.5.1':
optional: true
'@parcel/watcher-win32-x64@2.5.1':
optional: true
'@parcel/watcher@2.5.1':
dependencies:
detect-libc: 1.0.3
is-glob: 4.0.3
micromatch: 4.0.8
node-addon-api: 7.1.1
optionalDependencies:
'@parcel/watcher-android-arm64': 2.5.1
'@parcel/watcher-darwin-arm64': 2.5.1
'@parcel/watcher-darwin-x64': 2.5.1
'@parcel/watcher-freebsd-x64': 2.5.1
'@parcel/watcher-linux-arm-glibc': 2.5.1
'@parcel/watcher-linux-arm-musl': 2.5.1
'@parcel/watcher-linux-arm64-glibc': 2.5.1
'@parcel/watcher-linux-arm64-musl': 2.5.1
'@parcel/watcher-linux-x64-glibc': 2.5.1
'@parcel/watcher-linux-x64-musl': 2.5.1
'@parcel/watcher-win32-arm64': 2.5.1
'@parcel/watcher-win32-ia32': 2.5.1
'@parcel/watcher-win32-x64': 2.5.1
optional: true
'@pkgr/core@0.2.4': {}
'@rc-component/async-validator@5.0.4':
......@@ -2892,14 +3062,14 @@ snapshots:
'@ungap/structured-clone@1.3.0': {}
'@vitejs/plugin-react@4.4.1(vite@5.4.19)':
'@vitejs/plugin-react@4.4.1(vite@5.4.19(sass@1.87.0))':
dependencies:
'@babel/core': 7.26.10
'@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10)
'@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10)
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
vite: 5.4.19
vite: 5.4.19(sass@1.87.0)
transitivePeerDependencies:
- supports-color
......@@ -3109,6 +3279,10 @@ snapshots:
ansi-styles: 4.3.0
supports-color: 7.2.0
chokidar@4.0.3:
dependencies:
readdirp: 4.1.2
classnames@2.5.1: {}
color-convert@2.0.1:
......@@ -3189,6 +3363,9 @@ snapshots:
delayed-stream@1.0.0: {}
detect-libc@1.0.3:
optional: true
dir-glob@3.0.1:
dependencies:
path-type: 4.0.0
......@@ -3623,6 +3800,8 @@ snapshots:
immer@10.1.1: {}
immutable@5.1.1: {}
import-fresh@3.3.1:
dependencies:
parent-module: 1.0.1
......@@ -3848,6 +4027,9 @@ snapshots:
natural-compare@1.4.0: {}
node-addon-api@7.1.1:
optional: true
node-releases@2.0.19: {}
object-assign@4.1.1: {}
......@@ -4322,6 +4504,8 @@ snapshots:
dependencies:
loose-envify: 1.4.0
readdirp@4.1.2: {}
redux-persist@6.0.0(react@18.3.1)(redux@5.0.1):
dependencies:
redux: 5.0.1
......@@ -4429,6 +4613,14 @@ snapshots:
es-errors: 1.3.0
is-regex: 1.2.1
sass@1.87.0:
dependencies:
chokidar: 4.0.3
immutable: 5.1.1
source-map-js: 1.2.1
optionalDependencies:
'@parcel/watcher': 2.5.1
scheduler@0.23.2:
dependencies:
loose-envify: 1.4.0
......@@ -4648,13 +4840,14 @@ snapshots:
dependencies:
react: 18.3.1
vite@5.4.19:
vite@5.4.19(sass@1.87.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.3
rollup: 4.40.1
optionalDependencies:
fsevents: 2.3.3
sass: 1.87.0
which-boxed-primitive@1.1.1:
dependencies:
......
import { Routes } from 'react-router-dom';
import { shallowEqual } from 'react-redux';
import { Route, Routes } from 'react-router-dom';
import WrapperLayout from './components/layout/WrapperLayout';
import { ProtectedRoute } from './components/ui/routes/ProtectedRoute';
import Auth from './pages/auth/Auth';
import { useAppSelector } from './store/hooks';
function App() {
return <Routes></Routes>;
const { user } = useAppSelector((state) => state.user, shallowEqual);
return (
<Routes>
<Route
path="/"
element={
<ProtectedRoute user={user}>
<WrapperLayout />
</ProtectedRoute>
}
></Route>
<Route path="/auth" element={<Auth />} />
</Routes>
);
}
export default App;
import { MenuFoldOutlined, MenuUnfoldOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Layout, Menu, theme } from 'antd';
import { MenuItemType } from 'antd/es/menu/interface';
import { useState } from 'react';
import { Outlet } from 'react-router-dom';
const { Header, Sider, Content } = Layout;
const items: MenuItemType[] = [
{
key: '1',
icon: <UserOutlined />,
label: 'nav 1',
},
];
const WrapperLayout = () => {
const [collapsed, setCollapsed] = useState(false);
const {
token: { colorBgContainer, borderRadiusLG },
} = theme.useToken();
return (
<Layout style={{ height: '100vh' }}>
<Sider trigger={null} collapsible collapsed={collapsed}>
<div className="demo-logo-vertical" />
<Menu theme="dark" mode="inline" defaultSelectedKeys={['1']} items={items} />
</Sider>
<Layout>
<Header style={{ padding: 0, background: colorBgContainer }}>
<Button
type="text"
icon={collapsed ? <MenuUnfoldOutlined /> : <MenuFoldOutlined />}
onClick={() => setCollapsed(!collapsed)}
style={{
fontSize: '16px',
width: 64,
height: 64,
}}
/>
</Header>
<Content
style={{
margin: '24px 16px',
padding: 24,
minHeight: 280,
background: colorBgContainer,
borderRadius: borderRadiusLG,
}}
>
<Outlet />
</Content>
</Layout>
</Layout>
);
};
export default WrapperLayout;
import { LockOutlined, UserOutlined } from '@ant-design/icons';
import { Button, Checkbox, Flex, Form, Input } from 'antd';
export type AuthField = {
username: string;
password: string;
isLogin: boolean;
};
const AuthForm = () => {
const onFinish = (values: AuthField) => {
console.log('Received values of form: ', values);
};
return (
<Form
name="login"
initialValues={{ remember: true }}
style={{ maxWidth: 360 }}
onFinish={onFinish}
>
<Form.Item<AuthField>
name="username"
rules={[{ required: true, message: 'Please input your Username!' }]}
>
<Input prefix={<UserOutlined />} placeholder="Username" />
</Form.Item>
<Form.Item<AuthField>
name="password"
rules={[{ required: true, message: 'Please input your Password!' }]}
>
<Input prefix={<LockOutlined />} type="password" placeholder="Password" />
</Form.Item>
<Form.Item>
<Flex justify="space-between" align="center">
<Form.Item<AuthField> name="isLogin" valuePropName="checked" noStyle>
<Checkbox>Check this if login</Checkbox>
</Form.Item>
</Flex>
</Form.Item>
<Form.Item>
<Button block type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
);
};
export default AuthForm;
import React from 'react';
import { Navigate } from 'react-router-dom';
import { IUser } from '../../../types/user';
type ProtectedRouteProps = {
user: IUser | null;
children: React.ReactNode;
};
export const ProtectedRoute = ({ user, children }: ProtectedRouteProps) => {
if (!user) {
return <Navigate to="/auth" replace />;
}
return children;
};
import React from 'react'
import ReactDOM from 'react-dom/client'
import { Provider } from 'react-redux'
import { BrowserRouter } from 'react-router-dom'
import { PersistGate } from 'redux-persist/integration/react'
import App from './App.tsx'
import store, { persistor } from './store/index.tsx'
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import { PersistGate } from 'redux-persist/integration/react';
import App from './App.tsx';
import store, { persistor } from './store/index.tsx';
import './styles/main.scss';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
......
import AuthForm from '../../components/ui/form/AuthForm';
import '../../styles/auth.scss';
const Auth = () => {
return (
<div className="Auth">
<AuthForm />
</div>
);
};
export default Auth;
import { configureStore } from '@reduxjs/toolkit'
import { configureStore } from '@reduxjs/toolkit';
import {
FLUSH,
PAUSE,
......@@ -8,8 +8,9 @@ import {
PURGE,
REGISTER,
REHYDRATE,
} from 'redux-persist'
import storage from 'redux-persist/lib/storage'
} from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import userReducer from './slices/userSlice';
const persistConfig = {
key: 'root',
......@@ -18,11 +19,11 @@ const persistConfig = {
whitelist: ['user'],
};
const persistSlice = persistReducer(persistConfig);
const persistUser = persistReducer(persistConfig, userReducer);
const store = configureStore({
reducer: {
root: persistSlice
user: persistUser,
},
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
......
import { createSlice } from '@reduxjs/toolkit';
import { IUser } from '../../types/user';
interface State {
user: IUser | null;
error: Error | null;
loading: boolean;
}
const initialState: State = {
user: null,
error: null,
loading: false,
};
const userSlice = createSlice({
name: 'user',
initialState,
reducers: {},
});
export default userSlice.reducer;
.Auth {
height: 100vh;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
* {
margin: 0;
box-sizing: border-box;
}
export interface IUser {
id: number;
username: string;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment