Task: Add auth context and provider and add routes and route wrapper and more configs and utils
This commit is contained in:
parent
26451a4291
commit
85c81f5e2a
@ -1,3 +1,5 @@
|
|||||||
export const AppConfig = {
|
export const AppConfig = {
|
||||||
APP_NAME: process.env.REACT_APP_NAME
|
APP_NAME: process.env.REACT_APP_NAME,
|
||||||
|
|
||||||
|
CLIENT_TOKEN_KEY: "client_token",
|
||||||
}
|
}
|
131
src/context/AuthContext.tsx
Normal file
131
src/context/AuthContext.tsx
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
import { AppConfig } from '@/config'
|
||||||
|
import { printError, printInfo } from '@/utils/log_util'
|
||||||
|
import { getStorage, setStorage } from '@/utils/ls_util'
|
||||||
|
import React, { useContext, useCallback, useReducer, useEffect } from 'react'
|
||||||
|
|
||||||
|
interface AuthContextState {
|
||||||
|
login: (args: { username: string; password: string }) => Promise<void>
|
||||||
|
state: {
|
||||||
|
user: {
|
||||||
|
username: string
|
||||||
|
userId: string
|
||||||
|
} | null
|
||||||
|
}
|
||||||
|
getToken: () => string | undefined | null
|
||||||
|
logout: () => void
|
||||||
|
isLogin: () => boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
enum AuthActionType {
|
||||||
|
'LOGIN' = 'LOGIN',
|
||||||
|
'LOGOUT' = 'LOGOUT',
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthReducer: (
|
||||||
|
state: AuthContextState['state'],
|
||||||
|
action: {
|
||||||
|
type: AuthActionType
|
||||||
|
payload?: any
|
||||||
|
}
|
||||||
|
) => AuthContextState['state'] = (state, action) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case AuthActionType.LOGIN:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user: action.payload,
|
||||||
|
}
|
||||||
|
case AuthActionType.LOGOUT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
user: null,
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const AuthContext = React.createContext<AuthContextState>({
|
||||||
|
login: async () => {},
|
||||||
|
state: {
|
||||||
|
user: null,
|
||||||
|
},
|
||||||
|
getToken: () => {
|
||||||
|
return undefined
|
||||||
|
},
|
||||||
|
logout: () => {},
|
||||||
|
isLogin: () => { return false },
|
||||||
|
})
|
||||||
|
|
||||||
|
const AuthProvider: React.FC = (props) => {
|
||||||
|
const [state, dispatch] = useReducer(AuthReducer, {
|
||||||
|
user: null,
|
||||||
|
})
|
||||||
|
|
||||||
|
const login = async (args: { username: string; password: string }) => {
|
||||||
|
const res = {
|
||||||
|
data: {
|
||||||
|
token: 'DEMO_HAH',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
setStorage(AppConfig.CLIENT_TOKEN_KEY, res.data.token)
|
||||||
|
await verify()
|
||||||
|
dispatch({
|
||||||
|
type: AuthActionType.LOGIN,
|
||||||
|
payload: args,
|
||||||
|
})
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const doVerify = async () => {
|
||||||
|
const token = getStorage(AppConfig.CLIENT_TOKEN_KEY)
|
||||||
|
const userAuthInfo = {}
|
||||||
|
|
||||||
|
printInfo("User details =>", token, userAuthInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
doVerify()
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const verify = async () => {
|
||||||
|
const token = getStorage(AppConfig.CLIENT_TOKEN_KEY)
|
||||||
|
printError('Verify not implemented with token: ', token)
|
||||||
|
const userAuthInfo = {}
|
||||||
|
return userAuthInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
const logout = useCallback(() => {
|
||||||
|
setStorage(AppConfig.CLIENT_TOKEN_KEY, undefined)
|
||||||
|
dispatch({
|
||||||
|
type: AuthActionType.LOGOUT,
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const getToken = () => getStorage(AppConfig.CLIENT_TOKEN_KEY)
|
||||||
|
|
||||||
|
const isLogin = (): boolean => {
|
||||||
|
return getToken() != null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthContext.Provider
|
||||||
|
value={{
|
||||||
|
login,
|
||||||
|
state,
|
||||||
|
logout,
|
||||||
|
isLogin,
|
||||||
|
getToken,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</AuthContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthContext = () => {
|
||||||
|
return useContext(AuthContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthProvider
|
@ -2,8 +2,8 @@ import './index.less'
|
|||||||
|
|
||||||
export default function About() {
|
export default function About() {
|
||||||
return (
|
return (
|
||||||
<div className="about-title">
|
<div>
|
||||||
<h1>About Us</h1>
|
<h1 className="about-title">About Us</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
7
src/pages/Error/403.tsx
Normal file
7
src/pages/Error/403.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function AccessDenied() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Access denied!</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
7
src/pages/Error/404.tsx
Normal file
7
src/pages/Error/404.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function NotFound() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Not found!</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -2,8 +2,8 @@ import './index.less'
|
|||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div className="home-title">
|
<div>
|
||||||
<h1>Home</h1>
|
<h1 className="home-title">Home</h1>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
4
src/pages/Login/index.less
Normal file
4
src/pages/Login/index.less
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.login-title {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
9
src/pages/Login/index.tsx
Normal file
9
src/pages/Login/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import './index.less'
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1 className="login-title">Login</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
7
src/pages/Profile/index.tsx
Normal file
7
src/pages/Profile/index.tsx
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export default function Profile() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Profile</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
21
src/routes/AuthRoute.tsx
Normal file
21
src/routes/AuthRoute.tsx
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { useAuthContext } from '@/context/AuthContext'
|
||||||
|
import AccessDenied from '@/pages/Error/403'
|
||||||
|
import { Route } from 'react-router-dom'
|
||||||
|
|
||||||
|
const AuthRoute = (props: any) => {
|
||||||
|
const { component, ...rest } = props
|
||||||
|
const { isLogin } = useAuthContext()
|
||||||
|
|
||||||
|
if (!isLogin()) {
|
||||||
|
return (<AccessDenied />)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Route
|
||||||
|
{...rest}
|
||||||
|
render={component}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AuthRoute
|
@ -1,4 +1,6 @@
|
|||||||
|
import AuthProvider from '@/context/AuthContext'
|
||||||
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
|
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
|
||||||
|
import AuthRoute from './AuthRoute'
|
||||||
import { routes } from './routes'
|
import { routes } from './routes'
|
||||||
|
|
||||||
const RouterView = () => {
|
const RouterView = () => {
|
||||||
@ -6,16 +8,25 @@ const RouterView = () => {
|
|||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
{routes.map((route) => {
|
{routes.map((route) => {
|
||||||
return (
|
const { withAuthority } = route
|
||||||
<Route
|
if (withAuthority) {
|
||||||
key={route.key}
|
return (
|
||||||
exact={route.exact}
|
<AuthProvider key={route.key}>
|
||||||
component={route.component}
|
<AuthRoute {...route} />
|
||||||
children={route.children}
|
</AuthProvider>
|
||||||
location={route.location}
|
)
|
||||||
path={route.path}
|
} else {
|
||||||
/>
|
return (
|
||||||
)
|
<Route
|
||||||
|
key={route.key}
|
||||||
|
exact={route.exact}
|
||||||
|
component={route.component}
|
||||||
|
children={route.children}
|
||||||
|
location={route.location}
|
||||||
|
path={route.path}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
})}
|
})}
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
|
27
src/routes/interfaces.ts
Normal file
27
src/routes/interfaces.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { RouteProps } from 'react-router-dom'
|
||||||
|
|
||||||
|
export interface Authority {
|
||||||
|
authority?: string | undefined
|
||||||
|
strict?: boolean | undefined
|
||||||
|
with?: Authority | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AuthorityProps {
|
||||||
|
withAuthority?: boolean | undefined
|
||||||
|
authorities?: Authority | string[] | string | undefined
|
||||||
|
strictAuthority?: boolean | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CustomRouteProps extends RouteProps, AuthorityProps {
|
||||||
|
key: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MenuProps {
|
||||||
|
icon: any
|
||||||
|
label?: string | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SideSubMenuProps extends MenuProps {
|
||||||
|
key?: string | undefined
|
||||||
|
subMenus: SideSubMenuProps[]
|
||||||
|
}
|
@ -1,36 +1,18 @@
|
|||||||
import { RouteProps } from 'react-router-dom'
|
import About from '@/pages/About'
|
||||||
import About from '../pages/About'
|
import NotFound from '@/pages/Error/404'
|
||||||
import Home from '../pages/Home'
|
import Home from '@/pages/Home'
|
||||||
import RouteTypes from './types'
|
import Login from '@/pages/Login'
|
||||||
|
import Profile from '@/pages/Profile'
|
||||||
interface Authority {
|
import { CustomRouteProps } from '@/routes/interfaces'
|
||||||
authority?: string | undefined
|
import { RouteTypes } from '@/routes/types'
|
||||||
strict?: boolean | undefined
|
|
||||||
with?: Authority | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface AuthorityProps {
|
|
||||||
authorities?: Authority | string[] | string | undefined
|
|
||||||
strictAuthority?: boolean | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CustomRouteProps extends RouteProps, AuthorityProps {
|
|
||||||
key: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface MenuProps {
|
|
||||||
icon: any
|
|
||||||
label?: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SideMenuRouteProps extends CustomRouteProps, MenuProps {}
|
|
||||||
|
|
||||||
export interface SideSubMenuProps extends MenuProps {
|
|
||||||
key?: string | undefined
|
|
||||||
subMenus: SideSubMenuProps[]
|
|
||||||
}
|
|
||||||
|
|
||||||
const routes: CustomRouteProps[] = [
|
const routes: CustomRouteProps[] = [
|
||||||
|
// Auth
|
||||||
|
{
|
||||||
|
key: 'login',
|
||||||
|
path: RouteTypes.LOGIN,
|
||||||
|
component: () => <Login />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: 'home',
|
key: 'home',
|
||||||
exact: true,
|
exact: true,
|
||||||
@ -42,8 +24,20 @@ const routes: CustomRouteProps[] = [
|
|||||||
path: RouteTypes.ABOUT,
|
path: RouteTypes.ABOUT,
|
||||||
component: () => <About />,
|
component: () => <About />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
exact: true,
|
||||||
|
key: 'profile',
|
||||||
|
path: RouteTypes.PROFILE,
|
||||||
|
component: () => <Profile />,
|
||||||
|
withAuthority: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
{
|
||||||
|
key: 'notfound',
|
||||||
|
path: RouteTypes.ERROR_404,
|
||||||
|
component: () => <NotFound />,
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const menus: (SideMenuRouteProps | SideSubMenuProps)[] = []
|
export { routes }
|
||||||
|
|
||||||
export { routes, menus }
|
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
const RouteTypes = {
|
const RouteTypes = {
|
||||||
HOME: "/",
|
HOME: "/",
|
||||||
ABOUT: "/about",
|
ABOUT: "/about",
|
||||||
|
PROFILE: "/profile",
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
LOGIN: '/login',
|
||||||
|
|
||||||
|
// Errors
|
||||||
|
ERROR_404: "**",
|
||||||
}
|
}
|
||||||
|
|
||||||
export default RouteTypes
|
export {
|
||||||
|
RouteTypes,
|
||||||
|
}
|
3
src/utils/log_util.ts
Normal file
3
src/utils/log_util.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const printInfo = (message?: any, ...opts: any[]) => console.log(message, opts)
|
||||||
|
export const printError = (message?: any, ...opts: any[]) => console.error(message, opts)
|
||||||
|
export const printWarn = (message?: any, ...opts: any[]) => console.warn(message, opts)
|
7
src/utils/ls_util.ts
Normal file
7
src/utils/ls_util.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export const setStorage = (key: string, value?: any) => {
|
||||||
|
localStorage.setItem(key, JSON.stringify(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getStorage = (key: string, defaultValue?: string): (string | undefined | null) => {
|
||||||
|
return localStorage.getItem(key) ?? defaultValue
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user