Merge branch 'dev' into test
This commit is contained in:
@@ -1,4 +1,11 @@
|
|||||||
{
|
{
|
||||||
|
"exclude": [
|
||||||
|
"__tests__",
|
||||||
|
"\\.test\\.ts$",
|
||||||
|
"\\.test\\.tsx$",
|
||||||
|
"\\.spec\\.ts$",
|
||||||
|
"\\.spec\\.tsx$"
|
||||||
|
],
|
||||||
"directory": [
|
"directory": [
|
||||||
"./src/data/repositories",
|
"./src/data/repositories",
|
||||||
"./src/data/repositories/interfaces",
|
"./src/data/repositories/interfaces",
|
||||||
|
|||||||
@@ -7,15 +7,13 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core";
|
||||||
|
|
||||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||||
import { useUserDispatch } from "@/stores/user/user-context";
|
import { useUserDispatch } from "@/stores/user/user-context";
|
||||||
import { ROUTES } from "@/router/routes";
|
import { ROUTES } from "@/router/routes";
|
||||||
import styles from "./auth-screen.module.css";
|
|
||||||
|
|
||||||
import { AuthBackground } from "./auth-background";
|
import { AuthBackground, AuthPanel } from "./components";
|
||||||
import { AuthPanel } from "./auth-panel";
|
import styles from "./components/auth-screen.module.css";
|
||||||
|
|
||||||
export function AuthScreen() {
|
export function AuthScreen() {
|
||||||
const state = useAuthState();
|
const state = useAuthState();
|
||||||
@@ -11,11 +11,9 @@ export * from "./auth-legal-text";
|
|||||||
export * from "./auth-other-options-dialog";
|
export * from "./auth-other-options-dialog";
|
||||||
export * from "./auth-panel";
|
export * from "./auth-panel";
|
||||||
export * from "./auth-primary-button";
|
export * from "./auth-primary-button";
|
||||||
export * from "./auth-screen";
|
|
||||||
export * from "./auth-social-button";
|
export * from "./auth-social-button";
|
||||||
export * from "./auth-social-buttons";
|
export * from "./auth-social-buttons";
|
||||||
export * from "./auth-text-field";
|
export * from "./auth-text-field";
|
||||||
export * from "./auth-validators";
|
export * from "./auth-validators";
|
||||||
export * from "./email-login-form";
|
export * from "./email-login-form";
|
||||||
export * from "./email-register-form";
|
export * from "./email-register-form";
|
||||||
export * from "./__tests__/auth-validators.test";
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { AuthScreen } from "@/app/auth/components/auth-screen";
|
import { AuthScreen } from "@/app/auth/auth-screen";
|
||||||
|
|
||||||
export default function AuthPage() {
|
export default function AuthPage() {
|
||||||
return <AuthScreen />;
|
return <AuthScreen />;
|
||||||
|
|||||||
@@ -30,15 +30,17 @@ import type { LoginStatus } from "@/models/auth/login-status";
|
|||||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core";
|
||||||
|
|
||||||
import { BrowserHintOverlay } from "./browser-hint-overlay";
|
import {
|
||||||
import { ChatArea } from "./chat-area";
|
BrowserHintOverlay,
|
||||||
import { ChatHeader } from "./chat-header";
|
ChatArea,
|
||||||
import { ChatInputBar } from "./chat-input-bar";
|
ChatHeader,
|
||||||
import { ChatQuotaExhaustedBanner } from "./chat-quota-exhausted-banner";
|
ChatInputBar,
|
||||||
import { PwaInstallOverlay } from "./pwa-install-overlay";
|
ChatQuotaExhaustedBanner,
|
||||||
import styles from "./chat-screen.module.css";
|
PwaInstallOverlay,
|
||||||
|
} from "./components";
|
||||||
|
import styles from "./components/chat-screen.module.css";
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -9,7 +9,6 @@ export * from "./chat-header";
|
|||||||
export * from "./chat-input-bar";
|
export * from "./chat-input-bar";
|
||||||
export * from "./chat-input-text-field";
|
export * from "./chat-input-text-field";
|
||||||
export * from "./chat-quota-exhausted-banner";
|
export * from "./chat-quota-exhausted-banner";
|
||||||
export * from "./chat-screen";
|
|
||||||
export * from "./chat-send-button";
|
export * from "./chat-send-button";
|
||||||
export * from "./date-header";
|
export * from "./date-header";
|
||||||
export * from "./fullscreen-image-viewer";
|
export * from "./fullscreen-image-viewer";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ChatScreen } from "@/app/chat/components/chat-screen";
|
import { ChatScreen } from "@/app/chat/chat-screen";
|
||||||
|
|
||||||
export default function ChatPage() {
|
export default function ChatPage() {
|
||||||
return <ChatScreen />;
|
return <ChatScreen />;
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from "./back-bar";
|
export * from "./back-bar";
|
||||||
export * from "./sidebar-screen";
|
|
||||||
export * from "./user-header";
|
export * from "./user-header";
|
||||||
export * from "./vip-benefits-card";
|
export * from "./vip-benefits-card";
|
||||||
export * from "./voice-package-card";
|
export * from "./voice-package-card";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SidebarScreen } from "@/app/sidebar/components/sidebar-screen";
|
import { SidebarScreen } from "@/app/sidebar/sidebar-screen";
|
||||||
|
|
||||||
export default function SidebarPage() {
|
export default function SidebarPage() {
|
||||||
return <SidebarScreen />;
|
return <SidebarScreen />;
|
||||||
|
|||||||
@@ -3,18 +3,20 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell, SettingsSection } from "@/app/_components/core";
|
||||||
import { SettingsSection } from "@/app/_components/core/settings-section";
|
|
||||||
import { ROUTES } from "@/router/routes";
|
import { ROUTES } from "@/router/routes";
|
||||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||||
import { useUserDispatch, useUserState } from "@/stores/user/user-context";
|
import { useUserDispatch, useUserState } from "@/stores/user/user-context";
|
||||||
|
|
||||||
import { BackBar } from "./back-bar";
|
import {
|
||||||
import { UserHeader, type SidebarUserState } from "./user-header";
|
BackBar,
|
||||||
import { VipBenefitsCard } from "./vip-benefits-card";
|
UserHeader,
|
||||||
import { VoicePackageCard } from "./voice-package-card";
|
type SidebarUserState,
|
||||||
|
VipBenefitsCard,
|
||||||
|
VoicePackageCard,
|
||||||
|
} from "./components";
|
||||||
|
|
||||||
import styles from "./sidebar-screen.module.css";
|
import styles from "./components/sidebar-screen.module.css";
|
||||||
|
|
||||||
const FALLBACK_USERNAME = "User name";
|
const FALLBACK_USERNAME = "User name";
|
||||||
|
|
||||||
@@ -8,4 +8,3 @@ export * from "./splash-content";
|
|||||||
export * from "./splash-loading";
|
export * from "./splash-loading";
|
||||||
export * from "./splash-logo";
|
export * from "./splash-logo";
|
||||||
export * from "./splash-progress";
|
export * from "./splash-progress";
|
||||||
export * from "./splash-screen";
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { SplashScreen } from "@/app/splash/components/splash-screen";
|
import { SplashScreen } from "@/app/splash/splash-screen";
|
||||||
|
|
||||||
export default function SplashPage() {
|
export default function SplashPage() {
|
||||||
return <SplashScreen />;
|
return <SplashScreen />;
|
||||||
|
|||||||
@@ -3,18 +3,20 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
|
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
import { MobileShell } from "@/app/_components/core";
|
||||||
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
import { useAuthDispatch, useAuthState } from "@/stores/auth/auth-context";
|
||||||
import { useUserDispatch } from "@/stores/user/user-context";
|
import { useUserDispatch } from "@/stores/user/user-context";
|
||||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||||
import { ROUTES } from "@/router/routes";
|
import { ROUTES } from "@/router/routes";
|
||||||
import { pwaUtil } from "@/utils/pwa";
|
import { pwaUtil } from "@/utils/pwa";
|
||||||
|
|
||||||
import { SplashBackground } from "./splash-background";
|
import {
|
||||||
import { SplashLogo } from "./splash-logo";
|
SplashBackground,
|
||||||
import { SplashContent } from "./splash-content";
|
SplashButton,
|
||||||
import { SplashButton } from "./splash-button";
|
SplashContent,
|
||||||
import styles from "./splash-screen.module.css";
|
SplashLogo,
|
||||||
|
} from "./components";
|
||||||
|
import styles from "./components/splash-screen.module.css";
|
||||||
|
|
||||||
// 尽量早地缓存 beforeinstallprompt,避免用户停留 splash 时浏览器事件已触发并丢失。
|
// 尽量早地缓存 beforeinstallprompt,避免用户停留 splash 时浏览器事件已触发并丢失。
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== "undefined") {
|
||||||
@@ -9,5 +9,4 @@ export * from "./subscription-checkout-button";
|
|||||||
export * from "./subscription-cta-button";
|
export * from "./subscription-cta-button";
|
||||||
export * from "./subscription-manage-button";
|
export * from "./subscription-manage-button";
|
||||||
export * from "./subscription-plan-card";
|
export * from "./subscription-plan-card";
|
||||||
export * from "./subscription-screen";
|
|
||||||
export * from "./subscription-user-row";
|
export * from "./subscription-user-row";
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { SubscriptionScreen } from "@/app/subscription/components/subscription-screen";
|
import { SubscriptionScreen } from "@/app/subscription/subscription-screen";
|
||||||
|
|
||||||
export default function SubscriptionPage() {
|
export default function SubscriptionPage() {
|
||||||
return <SubscriptionScreen />;
|
return <SubscriptionScreen />;
|
||||||
|
|||||||
+11
-10
@@ -16,8 +16,7 @@
|
|||||||
*/
|
*/
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
import { Checkbox } from "@/app/_components/core/checkbox";
|
import { Checkbox, MobileShell } from "@/app/_components/core";
|
||||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
|
||||||
import { AppConstants } from "@/core/app_constants";
|
import { AppConstants } from "@/core/app_constants";
|
||||||
import {
|
import {
|
||||||
SUBSCRIPTION_PLANS,
|
SUBSCRIPTION_PLANS,
|
||||||
@@ -26,14 +25,16 @@ import {
|
|||||||
import { VIP_BENEFITS } from "@/data/constants/vip-benefits";
|
import { VIP_BENEFITS } from "@/data/constants/vip-benefits";
|
||||||
import { useUserState } from "@/stores/user/user-context";
|
import { useUserState } from "@/stores/user/user-context";
|
||||||
|
|
||||||
import { SubscriptionBackLink } from "./subscription-back-link";
|
import {
|
||||||
import { SubscriptionBanner } from "./subscription-banner";
|
SubscriptionBackLink,
|
||||||
import { SubscriptionBenefitsCard } from "./subscription-benefits-card";
|
SubscriptionBanner,
|
||||||
import { SubscriptionCheckoutButton } from "./subscription-checkout-button";
|
SubscriptionBenefitsCard,
|
||||||
import { SubscriptionManageButton } from "./subscription-manage-button";
|
SubscriptionCheckoutButton,
|
||||||
import { SubscriptionPlanCard } from "./subscription-plan-card";
|
SubscriptionManageButton,
|
||||||
import { SubscriptionUserRow } from "./subscription-user-row";
|
SubscriptionPlanCard,
|
||||||
import styles from "./subscription-screen.module.css";
|
SubscriptionUserRow,
|
||||||
|
} from "./components";
|
||||||
|
import styles from "./components/subscription-screen.module.css";
|
||||||
|
|
||||||
const FALLBACK_USERNAME = "User name";
|
const FALLBACK_USERNAME = "User name";
|
||||||
|
|
||||||
@@ -7,6 +7,7 @@
|
|||||||
* AuthProvider → UserProvider → SidebarProvider → ChatProvider
|
* AuthProvider → UserProvider → SidebarProvider → ChatProvider
|
||||||
* + AuthStatusChecker(启动时一次:派发 AuthInit)
|
* + AuthStatusChecker(启动时一次:派发 AuthInit)
|
||||||
* + OAuthSessionSync (持续监听 NextAuth session → auth machine)
|
* + OAuthSessionSync (持续监听 NextAuth session → auth machine)
|
||||||
|
* + UserAuthSync (auth 登录态恢复后 hydrate user machine)
|
||||||
*
|
*
|
||||||
* 它们始终挂载,保证各页面直接 `use*State()` / `use*Dispatch()` 即可,
|
* 它们始终挂载,保证各页面直接 `use*State()` / `use*Dispatch()` 即可,
|
||||||
* 无需在每个页面单独包裹。
|
* 无需在每个页面单独包裹。
|
||||||
@@ -19,6 +20,7 @@ import { AuthStatusChecker } from "@/stores/auth/auth-status-checker";
|
|||||||
import { OAuthSessionSync } from "@/stores/auth/oauth-session-sync";
|
import { OAuthSessionSync } from "@/stores/auth/oauth-session-sync";
|
||||||
import { ChatProvider } from "@/stores/chat/chat-context";
|
import { ChatProvider } from "@/stores/chat/chat-context";
|
||||||
import { SidebarProvider } from "@/stores/sidebar/sidebar-context";
|
import { SidebarProvider } from "@/stores/sidebar/sidebar-context";
|
||||||
|
import { UserAuthSync } from "@/stores/user/user-auth-sync";
|
||||||
import { UserProvider } from "@/stores/user/user-context";
|
import { UserProvider } from "@/stores/user/user-context";
|
||||||
|
|
||||||
export interface RootProvidersProps {
|
export interface RootProvidersProps {
|
||||||
@@ -34,6 +36,7 @@ export function RootProviders({ children }: RootProvidersProps) {
|
|||||||
{/* OAuthSessionSync 同样依赖 AuthProvider 上下文 */}
|
{/* OAuthSessionSync 同样依赖 AuthProvider 上下文 */}
|
||||||
<OAuthSessionSync />
|
<OAuthSessionSync />
|
||||||
<UserProvider>
|
<UserProvider>
|
||||||
|
<UserAuthSync />
|
||||||
<SidebarProvider>
|
<SidebarProvider>
|
||||||
<ChatProvider>
|
<ChatProvider>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auth → User 启动同步器
|
||||||
|
*
|
||||||
|
* AuthStatusChecker 只负责恢复 loginStatus;头像等登录用户资料由
|
||||||
|
* UserStorage 持久化在 user machine 这一侧。冷启动直达 /chat 时,
|
||||||
|
* 没有经过 splash/auth 的跳转副作用,因此需要在登录态恢复后主动
|
||||||
|
* hydrate user store。
|
||||||
|
*/
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import { useAuthState } from "@/stores/auth/auth-context";
|
||||||
|
|
||||||
|
import { useUserDispatch } from "./user-context";
|
||||||
|
|
||||||
|
export function UserAuthSync() {
|
||||||
|
const { loginStatus } = useAuthState();
|
||||||
|
const userDispatch = useUserDispatch();
|
||||||
|
const lastInitializedStatusRef = useRef<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (loginStatus === "notLoggedIn" || loginStatus === "guest") {
|
||||||
|
lastInitializedStatusRef.current = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastInitializedStatusRef.current === loginStatus) return;
|
||||||
|
lastInitializedStatusRef.current = loginStatus;
|
||||||
|
userDispatch({ type: "UserInit" });
|
||||||
|
}, [loginStatus, userDispatch]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user