refactor(chat): remove QuotaDialog and related state/effects
Drop the QuotaDialog component invocation in chat-screen along with its useState/useEffect wiring, and delete the component files. Update JSDoc in chat-screen and routes to reflect the removed consumer. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,14 +7,12 @@
|
||||
* 职责(本文件):
|
||||
* - **订阅 auth 状态机**(loginStatus)—— 派生 isGuest / **派鉴权生命周期事件**给 chat 机器
|
||||
* - 屏幕生命周期事件(init / visible / invisible)
|
||||
* - 配额告警监听(**仅**游客 —— "非游客无消息限制"业务事实)
|
||||
* - **不**直接管理 WebSocket / **不**直接读 AuthStorage(除 token 取值)
|
||||
*
|
||||
* 子组件职责:
|
||||
* - ChatHeader:顶部栏(游客 banner / 菜单按钮)
|
||||
* - ChatArea:消息列表
|
||||
* - ChatInputBar:输入栏(文字 + 发送 + More)
|
||||
* - QuotaDialog:配额告警弹窗(**仅**游客)
|
||||
* - PwaInstallOverlay:PWA 安装提示触发器
|
||||
* - BrowserHintOverlay:浏览器提示
|
||||
*
|
||||
@@ -26,14 +24,13 @@
|
||||
* - `ChatLogout` → 登出(机器自动 cleanup WS actor)
|
||||
* - chat 机器**内部**用 fromCallback actor 完整管理 WS 生命周期
|
||||
*/
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import Image from "next/image";
|
||||
|
||||
import { useAuthState } from "@/stores/auth/auth-context";
|
||||
import type { LoginStatus } from "@/models/auth/login-status";
|
||||
import { AuthStorage } from "@/data/storage/auth/auth_storage";
|
||||
import { useChatDispatch, useChatState } from "@/stores/chat/chat-context";
|
||||
import { GuestChatQuota } from "@/stores/chat";
|
||||
|
||||
import { MobileShell } from "@/app/_components/core/mobile-shell";
|
||||
|
||||
@@ -42,7 +39,6 @@ import { ChatArea } from "./chat-area";
|
||||
import { ChatHeader } from "./chat-header";
|
||||
import { ChatInputBar } from "./chat-input-bar";
|
||||
import { PwaInstallOverlay } from "./pwa-install-overlay";
|
||||
import { QuotaDialog } from "./quota-dialog";
|
||||
import styles from "./chat-screen.module.css";
|
||||
|
||||
/**
|
||||
@@ -88,9 +84,6 @@ export function ChatScreen() {
|
||||
// isGuest 派生(chat-screen 是**唯一**知道这个值的地方 —— chat 机器无 isGuest 字段)
|
||||
const isGuest = deriveIsGuest(authState.loginStatus);
|
||||
|
||||
const [quotaDialogOpen, setQuotaDialogOpen] = useState(false);
|
||||
const [quotaExhausted, setQuotaExhausted] = useState(false);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 屏幕生命周期 + 派初次鉴权生命周期事件
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
@@ -121,36 +114,6 @@ export function ChatScreen() {
|
||||
dispatchAuthLifecycle(authState.loginStatus, chatDispatch);
|
||||
}, [authState.loginStatus, chatDispatch]);
|
||||
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
// 配额耗尽触发器(**仅**游客 —— "非游客无消息限制")
|
||||
// ─────────────────────────────────────────────────────────────
|
||||
const prevTriggerRef = useRef(state.quotaExceededTrigger);
|
||||
useEffect(() => {
|
||||
if (!isGuest) return; // ← 防御:非游客 backend 不会发 ChatQuotaExceeded
|
||||
if (
|
||||
state.quotaExceededTrigger !== prevTriggerRef.current &&
|
||||
state.quotaExceededTrigger > 0
|
||||
) {
|
||||
setQuotaExhausted(true);
|
||||
setQuotaDialogOpen(true);
|
||||
}
|
||||
prevTriggerRef.current = state.quotaExceededTrigger;
|
||||
}, [state.quotaExceededTrigger, isGuest]);
|
||||
|
||||
// 配额警告(剩余 = 5)—— **仅**游客
|
||||
const prevRemainingRef = useRef(state.guestRemainingQuota);
|
||||
useEffect(() => {
|
||||
if (
|
||||
isGuest && // ← 派生自 auth.loginStatus
|
||||
state.guestRemainingQuota === GuestChatQuota.warningThreshold &&
|
||||
prevRemainingRef.current !== state.guestRemainingQuota
|
||||
) {
|
||||
setQuotaExhausted(false);
|
||||
setQuotaDialogOpen(true);
|
||||
}
|
||||
prevRemainingRef.current = state.guestRemainingQuota;
|
||||
}, [state.guestRemainingQuota, isGuest]);
|
||||
|
||||
return (
|
||||
<MobileShell>
|
||||
<div className={styles.shell}>
|
||||
@@ -183,13 +146,6 @@ export function ChatScreen() {
|
||||
|
||||
{/* PWA 安装提示触发器(无可见 UI,3.5s 后弹 dialog) */}
|
||||
<PwaInstallOverlay />
|
||||
|
||||
{/* 配额告警弹窗(**仅**游客,gate 在 isGuest) */}
|
||||
<QuotaDialog
|
||||
open={quotaDialogOpen}
|
||||
onClose={() => setQuotaDialogOpen(false)}
|
||||
isExhausted={quotaExhausted}
|
||||
/>
|
||||
</div>
|
||||
</MobileShell>
|
||||
);
|
||||
|
||||
@@ -1,85 +0,0 @@
|
||||
/* QuotaDialog 配额提示弹窗样式 */
|
||||
|
||||
.overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
width: calc(100% - 40px);
|
||||
max-width: 360px;
|
||||
background: var(--color-surface, #1f1a2e);
|
||||
border-radius: var(--radius-lg, 12px);
|
||||
padding: var(--spacing-5, 20px);
|
||||
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.4);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 32px;
|
||||
margin-bottom: var(--spacing-4, 16px);
|
||||
}
|
||||
|
||||
.iconExhausted {
|
||||
background: rgba(255, 80, 80, 0.2);
|
||||
color: #ff5050;
|
||||
}
|
||||
|
||||
.iconWarning {
|
||||
background: rgba(248, 77, 150, 0.2);
|
||||
color: var(--color-accent, #f84d96);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--font-size-lg, 18px);
|
||||
font-weight: 600;
|
||||
margin: 0 0 var(--spacing-2, 8px);
|
||||
color: var(--color-text-primary, #fff);
|
||||
}
|
||||
|
||||
.content {
|
||||
font-size: var(--font-size-sm, 14px);
|
||||
line-height: 1.5;
|
||||
color: var(--color-text-secondary, #9e9e9e);
|
||||
margin: 0 0 var(--spacing-5, 20px);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: var(--spacing-3, 12px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1 1 auto;
|
||||
height: 40px;
|
||||
border: 0;
|
||||
border-radius: var(--radius-md, 8px);
|
||||
font-size: var(--font-size-base, 14px);
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btnSecondary {
|
||||
background: transparent;
|
||||
color: var(--color-text-primary, #fff);
|
||||
}
|
||||
|
||||
.btnPrimary {
|
||||
background: var(--color-accent, #f84d96);
|
||||
color: #fff;
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
"use client";
|
||||
/**
|
||||
* QuotaDialog 配额提示弹窗
|
||||
*
|
||||
* 原始 Dart: lib/ui/chat/widgets/quota_dialog.dart(130 行)
|
||||
*
|
||||
* 两种模式:
|
||||
* - 警告模式(isExhausted=false):剩余配额 < 5 时,提示用户登录
|
||||
* - 耗尽模式(isExhausted=true):配额已用完,强制登录
|
||||
*/
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
import { ROUTES } from "@/router/routes";
|
||||
|
||||
import styles from "./quota-dialog.module.css";
|
||||
|
||||
export interface QuotaDialogProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
isExhausted: boolean;
|
||||
}
|
||||
|
||||
export function QuotaDialog({ open, onClose, isExhausted }: QuotaDialogProps) {
|
||||
const router = useRouter();
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
const title = isExhausted
|
||||
? "Daily quota exhausted"
|
||||
: "Login for unlimited access!";
|
||||
|
||||
const content = isExhausted
|
||||
? "Login to continue chatting~"
|
||||
: "Dear, sign up now and all your chats with Elio will be saved forever. In guest mode, your chat history will be deleted when you leave, and Elio will forget you.";
|
||||
|
||||
const handleLogin = () => {
|
||||
onClose();
|
||||
router.push(ROUTES.auth);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.overlay}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
aria-labelledby="quota-dialog-title"
|
||||
>
|
||||
<div className={styles.dialog}>
|
||||
<div
|
||||
className={`${styles.icon} ${isExhausted ? styles.iconExhausted : styles.iconWarning}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{isExhausted ? "🚫" : "⚠️"}
|
||||
</div>
|
||||
|
||||
<h2 id="quota-dialog-title" className={styles.title}>
|
||||
{title}
|
||||
</h2>
|
||||
<p className={styles.content}>{content}</p>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.btn} ${styles.btnSecondary}`}
|
||||
onClick={onClose}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={`${styles.btn} ${styles.btnPrimary}`}
|
||||
onClick={handleLogin}
|
||||
>
|
||||
Go to Login
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
* - 动态深链通过 `ROUTE_BUILDERS` 函数生成,避免手拼字符串。
|
||||
*
|
||||
* 注意:proxy 路由跳转逻辑(2026-06-15 迁出),但本文件的**类型化常量**仍在
|
||||
* 被 9 个组件(auth-screen / chat-header / quota-dialog / DeepLinkPersist /
|
||||
* 被 8 个组件(auth-screen / chat-header / DeepLinkPersist /
|
||||
* error / not-found / page / sidebar-screen / splash-screen)使用 —— **不能**整体注释。
|
||||
*/
|
||||
|
||||
|
||||
Reference in New Issue
Block a user