115 lines
3.3 KiB
TypeScript
115 lines
3.3 KiB
TypeScript
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from 'react';
|
|
import { apiAuth } from '../api';
|
|
|
|
export interface StaffUser {
|
|
id: number;
|
|
email: string;
|
|
fullName: string;
|
|
mustChangePassword: boolean;
|
|
}
|
|
|
|
interface AuthState {
|
|
token: string | null;
|
|
staff: StaffUser | null;
|
|
loading: boolean;
|
|
login: (email: string, password: string) => Promise<void>;
|
|
provision: (email: string) => Promise<string>;
|
|
logout: () => void;
|
|
changePassword: (oldPassword: string, newPassword: string) => Promise<void>;
|
|
forgotPassword: (email: string) => Promise<string>;
|
|
resetPassword: (token: string, newPassword: string) => Promise<void>;
|
|
refreshMe: () => Promise<void>;
|
|
}
|
|
|
|
const TOKEN_KEY = 'sc_staff_token';
|
|
const AuthContext = createContext<AuthState | null>(null);
|
|
|
|
export function AuthProvider({ children }: { children: ReactNode }) {
|
|
const [token, setToken] = useState<string | null>(() => localStorage.getItem(TOKEN_KEY));
|
|
const [staff, setStaff] = useState<StaffUser | null>(null);
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
const applySession = useCallback((nextToken: string | null, nextStaff: StaffUser | null) => {
|
|
setToken(nextToken);
|
|
setStaff(nextStaff);
|
|
if (nextToken) {
|
|
localStorage.setItem(TOKEN_KEY, nextToken);
|
|
} else {
|
|
localStorage.removeItem(TOKEN_KEY);
|
|
}
|
|
}, []);
|
|
|
|
const refreshMe = useCallback(async () => {
|
|
if (!token) {
|
|
setStaff(null);
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
try {
|
|
const res = await apiAuth.me();
|
|
setStaff(res.staff);
|
|
} catch {
|
|
applySession(null, null);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [token, applySession]);
|
|
|
|
useEffect(() => {
|
|
refreshMe();
|
|
}, [refreshMe]);
|
|
|
|
const login = useCallback(async (email: string, password: string) => {
|
|
const res = await apiAuth.login(email, password);
|
|
applySession(res.token, res.staff);
|
|
}, [applySession]);
|
|
|
|
const provision = useCallback(async (email: string) => {
|
|
const res = await apiAuth.provision(email);
|
|
return res.message;
|
|
}, []);
|
|
|
|
const logout = useCallback(() => {
|
|
applySession(null, null);
|
|
}, [applySession]);
|
|
|
|
const changePassword = useCallback(async (oldPassword: string, newPassword: string) => {
|
|
const res = await apiAuth.changePassword(oldPassword, newPassword);
|
|
applySession(res.token, res.staff);
|
|
}, [applySession]);
|
|
|
|
const forgotPassword = useCallback(async (email: string) => {
|
|
const res = await apiAuth.forgotPassword(email);
|
|
return res.message;
|
|
}, []);
|
|
|
|
const resetPassword = useCallback(async (resetToken: string, newPassword: string) => {
|
|
await apiAuth.resetPassword(resetToken, newPassword);
|
|
}, []);
|
|
|
|
const value = useMemo<AuthState>(() => ({
|
|
token,
|
|
staff,
|
|
loading,
|
|
login,
|
|
provision,
|
|
logout,
|
|
changePassword,
|
|
forgotPassword,
|
|
resetPassword,
|
|
refreshMe,
|
|
}), [token, staff, loading, login, provision, logout, changePassword, forgotPassword, resetPassword, refreshMe]);
|
|
|
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
}
|
|
|
|
export function useAuth() {
|
|
const ctx = useContext(AuthContext);
|
|
if (!ctx) throw new Error('useAuth must be used within AuthProvider');
|
|
return ctx;
|
|
}
|
|
|
|
export function getStoredToken(): string | null {
|
|
return localStorage.getItem(TOKEN_KEY);
|
|
}
|