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;
|
|||
|
|
}
|