97 lines
3.1 KiB
TypeScript
97 lines
3.1 KiB
TypeScript
"use client";
|
||
/**
|
||
* AuthContext:基于 XState v5 的 React Context Provider
|
||
*
|
||
* 业务事件(`Auth*Submitted`)通过 `useMachine(authMachine).send()` 派发。
|
||
* XState 自动处理异步副作用(invoke + fromPromise actors)。
|
||
*
|
||
* 兼容性:保持原 `useAuthState()` / `useAuthDispatch()` API,
|
||
* 内部将 XState 状态机快照映射回原 `AuthState` 形状。
|
||
*/
|
||
import {
|
||
type Dispatch,
|
||
type ReactNode,
|
||
createContext,
|
||
useContext,
|
||
useMemo,
|
||
} from "react";
|
||
import { useMachine } from "@xstate/react";
|
||
|
||
import { authMachine } from "./auth-machine";
|
||
import type { AuthEvent, AuthState as MachineContext } from "./auth-machine";
|
||
|
||
/**
|
||
* 对外暴露的 State 形状
|
||
*/
|
||
interface AuthState {
|
||
authPanelMode: MachineContext["authPanelMode"];
|
||
authMode: MachineContext["authMode"];
|
||
email: string;
|
||
password: string;
|
||
username: string;
|
||
confirmPassword: string;
|
||
isLoading: boolean;
|
||
errorMessage: string | null;
|
||
/** 当前登录状态(未登录 / 游客 / OAuth / 邮箱) */
|
||
loginStatus: MachineContext["loginStatus"];
|
||
/** 启动时的本地登录态恢复是否已经完成 */
|
||
hasInitialized: MachineContext["hasInitialized"];
|
||
}
|
||
|
||
const AuthStateCtx = createContext<AuthState | null>(null);
|
||
const AuthDispatchCtx = createContext<Dispatch<AuthEvent> | null>(null);
|
||
|
||
export interface AuthProviderProps {
|
||
children: ReactNode;
|
||
}
|
||
|
||
export function AuthProvider({ children }: AuthProviderProps) {
|
||
const [state, send] = useMachine(authMachine);
|
||
|
||
// 映射 XState 状态机快照 → 原 AuthState 形状
|
||
const authState = useMemo<AuthState>(
|
||
() => ({
|
||
authPanelMode: state.context.authPanelMode,
|
||
authMode: state.context.authMode,
|
||
email: state.context.email,
|
||
password: state.context.password,
|
||
username: state.context.username,
|
||
confirmPassword: state.context.confirmPassword,
|
||
// isLoading 覆盖邮箱登录 / 邮箱注册 / OAuth 跳转(NextAuth 重定向期间)/
|
||
// OAuth 回调后端 sync / 显式游客登录
|
||
isLoading:
|
||
state.matches("loadingEmailLogin") ||
|
||
state.matches("loadingEmailRegister") ||
|
||
state.matches("loadingOAuth") ||
|
||
state.matches("initializing") ||
|
||
state.matches("loggingOut") ||
|
||
state.matches("syncingGoogleBackend") ||
|
||
state.matches("syncingFacebookBackend") ||
|
||
state.matches("loadingGuestLogin"),
|
||
errorMessage: state.context.errorMessage,
|
||
loginStatus: state.context.loginStatus,
|
||
hasInitialized: state.context.hasInitialized,
|
||
}),
|
||
[state],
|
||
);
|
||
|
||
return (
|
||
<AuthStateCtx.Provider value={authState}>
|
||
<AuthDispatchCtx.Provider value={send}>{children}</AuthDispatchCtx.Provider>
|
||
</AuthStateCtx.Provider>
|
||
);
|
||
}
|
||
|
||
export function useAuthState(): AuthState {
|
||
const ctx = useContext(AuthStateCtx);
|
||
if (!ctx) throw new Error("useAuthState must be used inside <AuthProvider>");
|
||
return ctx;
|
||
}
|
||
|
||
export function useAuthDispatch(): Dispatch<AuthEvent> {
|
||
const ctx = useContext(AuthDispatchCtx);
|
||
if (!ctx)
|
||
throw new Error("useAuthDispatch must be used inside <AuthProvider>");
|
||
return ctx;
|
||
}
|