Task: Add auth context and provider and add routes and route wrapper and more configs and utils

This commit is contained in:
Sambo Chea 2021-06-12 19:22:48 +07:00
parent 26451a4291
commit 85c81f5e2a
16 changed files with 289 additions and 50 deletions

View File

@ -1,3 +1,5 @@
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
View 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

View File

@ -2,8 +2,8 @@ import './index.less'
export default function About() {
return (
<div className="about-title">
<h1>About Us</h1>
<div>
<h1 className="about-title">About Us</h1>
</div>
)
}

7
src/pages/Error/403.tsx Normal file
View File

@ -0,0 +1,7 @@
export default function AccessDenied() {
return (
<div>
<h1>Access denied!</h1>
</div>
)
}

7
src/pages/Error/404.tsx Normal file
View File

@ -0,0 +1,7 @@
export default function NotFound() {
return (
<div>
<h1>Not found!</h1>
</div>
)
}

View File

@ -2,8 +2,8 @@ import './index.less'
export default function Home() {
return (
<div className="home-title">
<h1>Home</h1>
<div>
<h1 className="home-title">Home</h1>
</div>
)
}

View File

@ -0,0 +1,4 @@
.login-title {
text-align: center;
font-weight: bold;
}

View File

@ -0,0 +1,9 @@
import './index.less'
export default function Login() {
return (
<div>
<h1 className="login-title">Login</h1>
</div>
)
}

View File

@ -0,0 +1,7 @@
export default function Profile() {
return (
<div>
<h1>Profile</h1>
</div>
)
}

21
src/routes/AuthRoute.tsx Normal file
View 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

View File

@ -1,4 +1,6 @@
import AuthProvider from '@/context/AuthContext'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import AuthRoute from './AuthRoute'
import { routes } from './routes'
const RouterView = () => {
@ -6,16 +8,25 @@ const RouterView = () => {
<Router>
<Switch>
{routes.map((route) => {
return (
<Route
key={route.key}
exact={route.exact}
component={route.component}
children={route.children}
location={route.location}
path={route.path}
/>
)
const { withAuthority } = route
if (withAuthority) {
return (
<AuthProvider key={route.key}>
<AuthRoute {...route} />
</AuthProvider>
)
} else {
return (
<Route
key={route.key}
exact={route.exact}
component={route.component}
children={route.children}
location={route.location}
path={route.path}
/>
)
}
})}
</Switch>
</Router>

27
src/routes/interfaces.ts Normal file
View 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[]
}

View File

@ -1,36 +1,18 @@
import { RouteProps } from 'react-router-dom'
import About from '../pages/About'
import Home from '../pages/Home'
import RouteTypes from './types'
interface Authority {
authority?: string | undefined
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[]
}
import About from '@/pages/About'
import NotFound from '@/pages/Error/404'
import Home from '@/pages/Home'
import Login from '@/pages/Login'
import Profile from '@/pages/Profile'
import { CustomRouteProps } from '@/routes/interfaces'
import { RouteTypes } from '@/routes/types'
const routes: CustomRouteProps[] = [
// Auth
{
key: 'login',
path: RouteTypes.LOGIN,
component: () => <Login />,
},
{
key: 'home',
exact: true,
@ -42,8 +24,20 @@ const routes: CustomRouteProps[] = [
path: RouteTypes.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, menus }
export { routes }

View File

@ -1,6 +1,15 @@
const RouteTypes = {
HOME: "/",
ABOUT: "/about",
PROFILE: "/profile",
// Auth
LOGIN: '/login',
// Errors
ERROR_404: "**",
}
export default RouteTypes
export {
RouteTypes,
}

3
src/utils/log_util.ts Normal file
View 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
View 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
}