97 lines
2.2 KiB
TypeScript
97 lines
2.2 KiB
TypeScript
|
|
import {
|
||
|
|
createContext,
|
||
|
|
useCallback,
|
||
|
|
useContext,
|
||
|
|
useEffect,
|
||
|
|
useMemo,
|
||
|
|
useState,
|
||
|
|
type ReactNode,
|
||
|
|
} from 'react';
|
||
|
|
import { clearTokens, getToken } from '../api/http';
|
||
|
|
import * as permApi from '../api/permission';
|
||
|
|
|
||
|
|
interface AuthState {
|
||
|
|
ready: boolean;
|
||
|
|
token: string;
|
||
|
|
uid: string;
|
||
|
|
roles: string[];
|
||
|
|
isAdmin: boolean;
|
||
|
|
syncSession: () => void;
|
||
|
|
refreshRoles: () => Promise<void>;
|
||
|
|
signOut: () => void;
|
||
|
|
}
|
||
|
|
|
||
|
|
const AuthContext = createContext<AuthState | null>(null);
|
||
|
|
|
||
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
||
|
|
const [ready, setReady] = useState(false);
|
||
|
|
const [token, setToken] = useState(getToken);
|
||
|
|
const [uid, setUid] = useState(() => localStorage.getItem('uid') ?? '');
|
||
|
|
const [roles, setRoles] = useState<string[]>(() => {
|
||
|
|
try {
|
||
|
|
return JSON.parse(localStorage.getItem('roles') ?? '[]') as string[];
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
const syncSession = useCallback(() => {
|
||
|
|
setToken(getToken());
|
||
|
|
setUid(localStorage.getItem('uid') ?? '');
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const refreshRoles = useCallback(async () => {
|
||
|
|
if (!getToken()) {
|
||
|
|
setRoles([]);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
try {
|
||
|
|
const me = await permApi.getMyPermissions();
|
||
|
|
setRoles(me.roles ?? []);
|
||
|
|
localStorage.setItem('roles', JSON.stringify(me.roles ?? []));
|
||
|
|
if (me.uid) {
|
||
|
|
setUid(me.uid);
|
||
|
|
localStorage.setItem('uid', me.uid);
|
||
|
|
}
|
||
|
|
} catch {
|
||
|
|
setRoles([]);
|
||
|
|
}
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
useEffect(() => {
|
||
|
|
(async () => {
|
||
|
|
if (getToken()) await refreshRoles();
|
||
|
|
setReady(true);
|
||
|
|
})();
|
||
|
|
}, [refreshRoles]);
|
||
|
|
|
||
|
|
const signOut = useCallback(() => {
|
||
|
|
clearTokens();
|
||
|
|
setToken('');
|
||
|
|
setUid('');
|
||
|
|
setRoles([]);
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
const value = useMemo(
|
||
|
|
() => ({
|
||
|
|
ready,
|
||
|
|
token,
|
||
|
|
uid,
|
||
|
|
roles,
|
||
|
|
isAdmin: permApi.isAdminRole(roles),
|
||
|
|
syncSession,
|
||
|
|
refreshRoles,
|
||
|
|
signOut,
|
||
|
|
}),
|
||
|
|
[ready, token, uid, roles, syncSession, refreshRoles, signOut],
|
||
|
|
);
|
||
|
|
|
||
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||
|
|
}
|
||
|
|
|
||
|
|
export function useAuth() {
|
||
|
|
const ctx = useContext(AuthContext);
|
||
|
|
if (!ctx) throw new Error('useAuth 需在 AuthProvider 內');
|
||
|
|
return ctx;
|
||
|
|
}
|