Files
cozsweet-frontend-nextjs/src/stores/auth/auth-context.tsx
T

97 lines
3.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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;
}