refactor: migrate state imports from contexts to stores directory
This commit is contained in:
@@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useAuthState } from "@/contexts/auth/auth-context";
|
import { useAuthState } from "@/stores/auth/auth-context";
|
||||||
|
|
||||||
import { EmailLoginForm } from "./email-login-form";
|
import { EmailLoginForm } from "./email-login-form";
|
||||||
import { EmailRegisterForm } from "./email-register-form";
|
import { EmailRegisterForm } from "./email-register-form";
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useAuthState, useAuthDispatch } from "@/contexts/auth/auth-context";
|
import { useAuthState, useAuthDispatch } from "@/stores/auth/auth-context";
|
||||||
|
|
||||||
import { AuthSocialButtons } from "./auth-social-buttons";
|
import { AuthSocialButtons } from "./auth-social-buttons";
|
||||||
import { AuthDivider } from "./auth-divider";
|
import { AuthDivider } from "./auth-divider";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*
|
*
|
||||||
* 原始 Dart: lib/ui/auth/widgets/auth_panel.dart
|
* 原始 Dart: lib/ui/auth/widgets/auth_panel.dart
|
||||||
*/
|
*/
|
||||||
import { useAuthState } from "@/contexts/auth/auth-context";
|
import { useAuthState } from "@/stores/auth/auth-context";
|
||||||
|
|
||||||
import { AuthFacebookPanel } from "./auth-facebook-panel";
|
import { AuthFacebookPanel } from "./auth-facebook-panel";
|
||||||
import { AuthEmailPanel } from "./auth-email-panel";
|
import { AuthEmailPanel } from "./auth-email-panel";
|
||||||
|
|||||||
@@ -13,9 +13,9 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { useAuthState, useAuthDispatch } from "@/contexts/auth/auth-context";
|
import { useAuthState, useAuthDispatch } from "@/stores/auth/auth-context";
|
||||||
import { useChatDispatch } from "@/contexts/chat/chat-context";
|
import { useChatDispatch } from "@/stores/chat/chat-context";
|
||||||
import { useUserDispatch } from "@/contexts/user/user-context";
|
import { useUserDispatch } from "@/stores/user/user-context";
|
||||||
import { useAuthGate } from "@/lib/auth/use-auth-gate";
|
import { useAuthGate } from "@/lib/auth/use-auth-gate";
|
||||||
import { ROUTES } from "@/lib/routes";
|
import { ROUTES } from "@/lib/routes";
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useAuthDispatch } from "@/contexts/auth/auth-context";
|
import { useAuthDispatch } from "@/stores/auth/auth-context";
|
||||||
import {
|
import {
|
||||||
validateEmail,
|
validateEmail,
|
||||||
validatePassword,
|
validatePassword,
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { useAuthDispatch } from "@/contexts/auth/auth-context";
|
import { useAuthDispatch } from "@/stores/auth/auth-context";
|
||||||
import {
|
import {
|
||||||
validateConfirmPassword,
|
validateConfirmPassword,
|
||||||
validateEmail,
|
validateEmail,
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import { type FormEvent, useEffect, useRef, useState } from "react";
|
|||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||||
import { Dialog } from "@/app/_components/core/dialog";
|
import { Dialog } from "@/app/_components/core/dialog";
|
||||||
import { useChatDispatch, useChatState } from "@/contexts/chat/chat-context";
|
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||||
import { GuestChatQuota } from "@/contexts/chat/chat-types";
|
import { GuestChatQuota } from "@/stores/chat/chat-types";
|
||||||
import type { UiMessage } from "@/models/chat/ui-message";
|
import type { UiMessage } from "@/models/chat/ui-message";
|
||||||
|
|
||||||
import styles from "./chat-screen.module.css";
|
import styles from "./chat-screen.module.css";
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useEffect } from "react";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||||
import { useUserDispatch, useUserState } from "@/contexts/user/user-context";
|
import { useUserDispatch, useUserState } from "@/stores/user/user-context";
|
||||||
|
|
||||||
import styles from "./sidebar-screen.module.css";
|
import styles from "./sidebar-screen.module.css";
|
||||||
|
|
||||||
|
|||||||
@@ -10,18 +10,25 @@
|
|||||||
* - 居中布局 (MainAxisAlignment.center)
|
* - 居中布局 (MainAxisAlignment.center)
|
||||||
* - Facebook 按钮:带渐变(primaryGradient)+ 圆角 24 + 高度 48
|
* - Facebook 按钮:带渐变(primaryGradient)+ 圆角 24 + 高度 48
|
||||||
* - 加载中:按钮内显示 spinner
|
* - 加载中:按钮内显示 spinner
|
||||||
|
*
|
||||||
|
* Skip 按钮使用 `<Link>` 而非 `<button onClick>`:
|
||||||
|
* - 保留 Next.js SPA 体验 + prefetch
|
||||||
|
* - 避免编程式 `router.push`(消除水合后副作用)
|
||||||
|
* - 右键"在新标签页打开"等浏览器行为自然支持
|
||||||
*/
|
*/
|
||||||
import { useAuthState, useAuthDispatch } from "@/contexts/auth/auth-context";
|
import Link from "next/link";
|
||||||
|
import { useAuthState, useAuthDispatch } from "@/stores/auth/auth-context";
|
||||||
import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
|
import { LoadingIndicator } from "@/app/_components/core/loading-indicator";
|
||||||
import styles from "./splash-button.module.css";
|
import styles from "./splash-button.module.css";
|
||||||
|
|
||||||
export interface SplashButtonProps {
|
export interface SplashButtonProps {
|
||||||
onSkip: () => void;
|
/** Skip 链接的目标路由(href) */
|
||||||
|
skipHref: string;
|
||||||
onFacebookLogin: () => void;
|
onFacebookLogin: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function SplashButton({
|
export function SplashButton({
|
||||||
onSkip,
|
skipHref,
|
||||||
onFacebookLogin,
|
onFacebookLogin,
|
||||||
}: SplashButtonProps) {
|
}: SplashButtonProps) {
|
||||||
const state = useAuthState();
|
const state = useAuthState();
|
||||||
@@ -30,15 +37,16 @@ export function SplashButton({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
{/* Skip 文本按钮 */}
|
{/* Skip 文本链接(next/link 渲染为 <a>,保留 SPA 体验) */}
|
||||||
<button
|
<Link
|
||||||
type="button"
|
href={skipHref}
|
||||||
onClick={onSkip}
|
|
||||||
className={styles.skip}
|
className={styles.skip}
|
||||||
disabled={isLoading}
|
aria-disabled={isLoading}
|
||||||
|
tabIndex={isLoading ? -1 : 0}
|
||||||
|
style={isLoading ? { pointerEvents: "none", opacity: 0.5 } : undefined}
|
||||||
>
|
>
|
||||||
Skip
|
Skip
|
||||||
</button>
|
</Link>
|
||||||
|
|
||||||
<div className={styles.separator} aria-hidden="true" />
|
<div className={styles.separator} aria-hidden="true" />
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
import styles from "./splash-content.module.css";
|
import styles from "./splash-content.module.css";
|
||||||
|
|
||||||
const APOSTROPHE = "'";
|
const APOSTROPHE = "'";
|
||||||
|
|
||||||
export function SplashContent() {
|
export function SplashContent() {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,11 +1,8 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useAuthState } from "@/stores/auth/auth-context";
|
||||||
import { useEffect, useRef } from "react";
|
import { useChatDispatch } from "@/stores/chat/chat-context";
|
||||||
|
import { useUserDispatch } from "@/stores/user/user-context";
|
||||||
import { useAuthState } from "@/contexts/auth/auth-context";
|
|
||||||
import { useChatDispatch } from "@/contexts/chat/chat-context";
|
|
||||||
import { useUserDispatch } from "@/contexts/user/user-context";
|
|
||||||
import { useAuthGate } from "@/lib/auth/use-auth-gate";
|
import { useAuthGate } from "@/lib/auth/use-auth-gate";
|
||||||
import { ROUTES } from "@/lib/routes";
|
import { ROUTES } from "@/lib/routes";
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||||
@@ -17,26 +14,31 @@ import { SplashButton } from "./splash-button";
|
|||||||
import styles from "./splash-screen.module.css";
|
import styles from "./splash-screen.module.css";
|
||||||
|
|
||||||
export function SplashScreen() {
|
export function SplashScreen() {
|
||||||
const router = useRouter();
|
// useAuthGate 仍消费(仅读取 isAuthed 用于子组件 gating,不触发跳转)
|
||||||
const { isAuthed } = useAuthGate();
|
useAuthGate();
|
||||||
|
// 以下 state/dispatch 当前未在本组件消费;保留以便未来按状态驱动的跳转
|
||||||
|
// 重新启用时再解注释 useEffect 即可。
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const state = useAuthState();
|
const state = useAuthState();
|
||||||
const wasSuccess = useRef(false);
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const chatDispatch = useChatDispatch();
|
const chatDispatch = useChatDispatch();
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
const userDispatch = useUserDispatch();
|
const userDispatch = useUserDispatch();
|
||||||
|
|
||||||
useEffect(() => {
|
// 已登录时跳 /chat(已注释:改由 SplashButton 的 Link + 路由守卫统一处理)
|
||||||
if (isAuthed) router.replace(ROUTES.chat);
|
// useEffect(() => {
|
||||||
}, [isAuthed, router]);
|
// if (isAuthed) router.replace(ROUTES.chat);
|
||||||
|
// }, [isAuthed, router]);
|
||||||
|
|
||||||
// 登录成功跳 /chat
|
// 登录成功跳 /chat(已注释:改由 SplashButton 的 Link + 路由守卫统一处理)
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
if (state.isSuccess && !wasSuccess.current) {
|
// if (state.isSuccess && !wasSuccess.current) {
|
||||||
wasSuccess.current = true;
|
// wasSuccess.current = true;
|
||||||
chatDispatch({ type: "ChatAuthStatusChanged" });
|
// chatDispatch({ type: "ChatAuthStatusChanged" });
|
||||||
userDispatch({ type: "UserInit" });
|
// userDispatch({ type: "UserInit" });
|
||||||
router.replace(ROUTES.chat);
|
// router.replace(ROUTES.chat);
|
||||||
}
|
// }
|
||||||
}, [state.isSuccess, chatDispatch, userDispatch, router]);
|
// }, [state.isSuccess, chatDispatch, userDispatch, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MobileShell background="var(--color-sidebar-background)">
|
<MobileShell background="var(--color-sidebar-background)">
|
||||||
@@ -50,7 +52,7 @@ export function SplashScreen() {
|
|||||||
<SplashContent />
|
<SplashContent />
|
||||||
<div className={styles.buttonArea}>
|
<div className={styles.buttonArea}>
|
||||||
<SplashButton
|
<SplashButton
|
||||||
onSkip={() => router.push(ROUTES.chat)}
|
skipHref={ROUTES.chat}
|
||||||
onFacebookLogin={() => {
|
onFacebookLogin={() => {
|
||||||
/* Facebook 登录触发由 SplashButton 内部 dispatch */
|
/* Facebook 登录触发由 SplashButton 内部 dispatch */
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -12,10 +12,10 @@
|
|||||||
|
|
||||||
import type { ReactNode } from "react";
|
import type { ReactNode } from "react";
|
||||||
|
|
||||||
import { AuthProvider } from "@/contexts/auth/auth-context";
|
import { AuthProvider } from "@/stores/auth/auth-context";
|
||||||
import { ChatProvider } from "@/contexts/chat/chat-context";
|
import { ChatProvider } from "@/stores/chat/chat-context";
|
||||||
import { SidebarProvider } from "@/contexts/sidebar/sidebar-context";
|
import { SidebarProvider } from "@/stores/sidebar/sidebar-context";
|
||||||
import { UserProvider } from "@/contexts/user/user-context";
|
import { UserProvider } from "@/stores/user/user-context";
|
||||||
|
|
||||||
export interface RootProvidersProps {
|
export interface RootProvidersProps {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
|||||||
+14
-2
@@ -11,15 +11,27 @@
|
|||||||
*/
|
*/
|
||||||
import { createStorage, type Storage } from "unstorage";
|
import { createStorage, type Storage } from "unstorage";
|
||||||
import localStorageDriver from "unstorage/drivers/localstorage";
|
import localStorageDriver from "unstorage/drivers/localstorage";
|
||||||
|
import memoryDriver from "unstorage/drivers/memory";
|
||||||
import type { ZodType } from "zod";
|
import type { ZodType } from "zod";
|
||||||
|
|
||||||
import { Result, type Result as ResultT } from "@/utils/result";
|
import { Result, type Result as ResultT } from "@/utils/result";
|
||||||
|
|
||||||
export class SpAsyncUtil {
|
export class SpAsyncUtil {
|
||||||
// ===== 私有静态状态 =====
|
// ===== 私有静态状态 =====
|
||||||
private static _storage: Storage = createStorage({
|
// 环境判断 + lazy 初始化:SSR 用 memory driver,浏览器用 localStorage driver。
|
||||||
driver: localStorageDriver({ base: "cozsweet:" }),
|
// unstorage 1.10+ 要求显式传入 localStorage,故这里同步注入 window.localStorage。
|
||||||
|
private static _storage: Storage = (() => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
// SSR 环境:无 window,降级为内存驱动
|
||||||
|
return createStorage({ driver: memoryDriver() });
|
||||||
|
}
|
||||||
|
// 浏览器环境:使用 localStorage 驱动
|
||||||
|
return createStorage({
|
||||||
|
driver: localStorageDriver({
|
||||||
|
base: "cozsweet:",
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
// 私有构造函数:禁止实例化(对齐 Dart `SpAsyncUtil._()`)
|
// 私有构造函数:禁止实例化(对齐 Dart `SpAsyncUtil._()`)
|
||||||
private constructor() {}
|
private constructor() {}
|
||||||
|
|||||||
Reference in New Issue
Block a user