diff --git a/src/config/index.ts b/src/config/index.ts index 1a617d9..97297e6 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -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", } \ No newline at end of file diff --git a/src/context/AuthContext.tsx b/src/context/AuthContext.tsx new file mode 100644 index 0000000..4d75790 --- /dev/null +++ b/src/context/AuthContext.tsx @@ -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 + 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({ + 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 ( + + {props.children} + + ) +} + +export const useAuthContext = () => { + return useContext(AuthContext) +} + +export default AuthProvider diff --git a/src/pages/About/index.tsx b/src/pages/About/index.tsx index 6a9c4fb..409b531 100644 --- a/src/pages/About/index.tsx +++ b/src/pages/About/index.tsx @@ -2,8 +2,8 @@ import './index.less' export default function About() { return ( -
-

About Us

+
+

About Us

) } diff --git a/src/pages/Error/403.tsx b/src/pages/Error/403.tsx new file mode 100644 index 0000000..9076439 --- /dev/null +++ b/src/pages/Error/403.tsx @@ -0,0 +1,7 @@ +export default function AccessDenied() { + return ( +
+

Access denied!

+
+ ) +} diff --git a/src/pages/Error/404.tsx b/src/pages/Error/404.tsx new file mode 100644 index 0000000..b75f137 --- /dev/null +++ b/src/pages/Error/404.tsx @@ -0,0 +1,7 @@ +export default function NotFound() { + return ( +
+

Not found!

+
+ ) +} diff --git a/src/pages/Home/index.tsx b/src/pages/Home/index.tsx index 72f6dfd..f454ede 100644 --- a/src/pages/Home/index.tsx +++ b/src/pages/Home/index.tsx @@ -2,8 +2,8 @@ import './index.less' export default function Home() { return ( -
-

Home

+
+

Home

) } diff --git a/src/pages/Login/index.less b/src/pages/Login/index.less new file mode 100644 index 0000000..f0a9348 --- /dev/null +++ b/src/pages/Login/index.less @@ -0,0 +1,4 @@ +.login-title { + text-align: center; + font-weight: bold; +} diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx new file mode 100644 index 0000000..e4074c4 --- /dev/null +++ b/src/pages/Login/index.tsx @@ -0,0 +1,9 @@ +import './index.less' + +export default function Login() { + return ( +
+

Login

+
+ ) +} diff --git a/src/pages/Profile/index.tsx b/src/pages/Profile/index.tsx new file mode 100644 index 0000000..292bf42 --- /dev/null +++ b/src/pages/Profile/index.tsx @@ -0,0 +1,7 @@ +export default function Profile() { + return ( +
+

Profile

+
+ ) +} diff --git a/src/routes/AuthRoute.tsx b/src/routes/AuthRoute.tsx new file mode 100644 index 0000000..e1c2139 --- /dev/null +++ b/src/routes/AuthRoute.tsx @@ -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 () + } + + return ( + + ) +} + +export default AuthRoute diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 504534b..f3f0e86 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -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 = () => { {routes.map((route) => { - return ( - - ) + const { withAuthority } = route + if (withAuthority) { + return ( + + + + ) + } else { + return ( + + ) + } })} diff --git a/src/routes/interfaces.ts b/src/routes/interfaces.ts new file mode 100644 index 0000000..ce85d7a --- /dev/null +++ b/src/routes/interfaces.ts @@ -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[] +} diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx index f48616d..3fd5a69 100644 --- a/src/routes/routes.tsx +++ b/src/routes/routes.tsx @@ -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: () => , + }, { key: 'home', exact: true, @@ -42,8 +24,20 @@ const routes: CustomRouteProps[] = [ path: RouteTypes.ABOUT, component: () => , }, + { + exact: true, + key: 'profile', + path: RouteTypes.PROFILE, + component: () => , + withAuthority: true, + }, + + // Errors + { + key: 'notfound', + path: RouteTypes.ERROR_404, + component: () => , + }, ] -const menus: (SideMenuRouteProps | SideSubMenuProps)[] = [] - -export { routes, menus } +export { routes } diff --git a/src/routes/types.ts b/src/routes/types.ts index 715bd6a..ef8de34 100644 --- a/src/routes/types.ts +++ b/src/routes/types.ts @@ -1,6 +1,15 @@ const RouteTypes = { HOME: "/", ABOUT: "/about", + PROFILE: "/profile", + + // Auth + LOGIN: '/login', + + // Errors + ERROR_404: "**", } -export default RouteTypes \ No newline at end of file +export { + RouteTypes, +} \ No newline at end of file diff --git a/src/utils/log_util.ts b/src/utils/log_util.ts new file mode 100644 index 0000000..d3cc5db --- /dev/null +++ b/src/utils/log_util.ts @@ -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) \ No newline at end of file diff --git a/src/utils/ls_util.ts b/src/utils/ls_util.ts new file mode 100644 index 0000000..e112993 --- /dev/null +++ b/src/utils/ls_util.ts @@ -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 +} \ No newline at end of file