80 lines
2.1 KiB
TypeScript
80 lines
2.1 KiB
TypeScript
import {
|
||
createContext,
|
||
useCallback,
|
||
useContext,
|
||
useEffect,
|
||
useMemo,
|
||
useState,
|
||
type ReactNode,
|
||
} from "react";
|
||
import type { Member } from "../domain/types";
|
||
import type { MemberProfilePatch } from "../data/repos";
|
||
import { useRepos } from "../data/DataContext";
|
||
|
||
type AuthContextValue = {
|
||
member: Member | null;
|
||
loading: boolean;
|
||
login: (email: string, password: string) => Promise<void>;
|
||
logout: () => Promise<void>;
|
||
reload: () => Promise<void>;
|
||
updateProfile: (patch: MemberProfilePatch) => Promise<Member>;
|
||
};
|
||
|
||
const AuthContext = createContext<AuthContextValue | null>(null);
|
||
|
||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||
const repos = useRepos();
|
||
const [member, setMember] = useState<Member | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const reload = useCallback(async () => {
|
||
// 已進過 app 後不要再把 loading=true:否則 RequireAuth 會卸載整棵 /app,
|
||
// 各頁本地 state(如用量「全體」分頁)會被重掛成預設。
|
||
try {
|
||
const me = await repos.auth.me();
|
||
setMember(me);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [repos.auth]);
|
||
|
||
useEffect(() => {
|
||
void reload();
|
||
}, [reload]);
|
||
|
||
const login = useCallback(
|
||
async (email: string, password: string) => {
|
||
const { member: m } = await repos.auth.login(email, password);
|
||
setMember(m);
|
||
},
|
||
[repos.auth],
|
||
);
|
||
|
||
const logout = useCallback(async () => {
|
||
await repos.auth.logout();
|
||
setMember(null);
|
||
}, [repos.auth]);
|
||
|
||
const updateProfile = useCallback(
|
||
async (patch: MemberProfilePatch) => {
|
||
const next = await repos.auth.updateProfile(patch);
|
||
setMember(next);
|
||
return next;
|
||
},
|
||
[repos.auth],
|
||
);
|
||
|
||
const value = useMemo(
|
||
() => ({ member, loading, login, logout, reload, updateProfile }),
|
||
[member, loading, login, logout, reload, updateProfile],
|
||
);
|
||
|
||
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||
}
|
||
|
||
export function useAuth(): AuthContextValue {
|
||
const ctx = useContext(AuthContext);
|
||
if (!ctx) throw new Error("useAuth must be used within AuthProvider");
|
||
return ctx;
|
||
}
|