From 2bc2e1b691945ad60d93d4a39635099757cd9948 Mon Sep 17 00:00:00 2001 From: chenhang Date: Wed, 10 Jun 2026 19:16:41 +0800 Subject: [PATCH] refactor: migrate img tags to next/image and fix hook usage --- src/app/auth/components/auth-email-panel.tsx | 6 +++++- src/app/auth/components/auth-facebook-panel.tsx | 6 +++++- src/app/chat/components/browser-hint-overlay.tsx | 9 +++------ src/app/chat/components/chat-area.tsx | 14 ++++++-------- .../chat/components/fullscreen-image-viewer.tsx | 7 +++++-- src/app/chat/components/image-bubble.tsx | 6 ++++-- src/app/chat/components/message-avatar.tsx | 3 +-- src/app/sidebar/components/sidebar-screen.tsx | 4 ++-- src/stores/auth/{machine => }/auth-context.tsx | 0 src/stores/auth/{machine => }/auth-events.ts | 0 src/stores/auth/{machine => }/auth-machine.ts | 6 +++--- src/stores/auth/auth-types.ts | 2 +- src/stores/auth/index.ts | 4 ++-- src/stores/chat/chat-context.tsx | 6 +++--- src/stores/chat/{machine => }/chat-events.ts | 0 src/stores/chat/{machine => }/chat-machine.ts | 4 ++-- src/stores/chat/{machine => }/chat-state.ts | 0 src/stores/chat/chat-types.ts | 2 +- src/stores/chat/index.ts | 4 ++-- src/stores/sidebar/index.ts | 2 +- src/stores/sidebar/sidebar-context.tsx | 6 +++--- src/stores/sidebar/{machine => }/sidebar-events.ts | 0 .../sidebar/{machine => }/sidebar-machine.ts | 6 +++--- src/stores/sidebar/{machine => }/sidebar-state.ts | 2 +- src/stores/sidebar/sidebar-types.ts | 2 +- src/stores/user/index.ts | 4 ++-- src/stores/user/{machine => }/user-events.ts | 0 src/stores/user/{machine => }/user-machine.ts | 10 +++++----- src/stores/user/{machine => }/user-state.ts | 0 src/stores/user/user-types.ts | 2 +- 30 files changed, 62 insertions(+), 55 deletions(-) rename src/stores/auth/{machine => }/auth-context.tsx (100%) rename src/stores/auth/{machine => }/auth-events.ts (100%) rename src/stores/auth/{machine => }/auth-machine.ts (97%) rename src/stores/chat/{machine => }/chat-events.ts (100%) rename src/stores/chat/{machine => }/chat-machine.ts (98%) rename src/stores/chat/{machine => }/chat-state.ts (100%) rename src/stores/sidebar/{machine => }/sidebar-events.ts (100%) rename src/stores/sidebar/{machine => }/sidebar-machine.ts (93%) rename src/stores/sidebar/{machine => }/sidebar-state.ts (87%) rename src/stores/user/{machine => }/user-events.ts (100%) rename src/stores/user/{machine => }/user-machine.ts (94%) rename src/stores/user/{machine => }/user-state.ts (100%) diff --git a/src/app/auth/components/auth-email-panel.tsx b/src/app/auth/components/auth-email-panel.tsx index 7f9af2ae..f31548c6 100644 --- a/src/app/auth/components/auth-email-panel.tsx +++ b/src/app/auth/components/auth-email-panel.tsx @@ -13,6 +13,7 @@ * - Spacer + AuthLegalText * - 顶部 "← Back" 按钮由父级 AuthPanel 渲染(悬浮 40×40 圆形) */ +import Image from "next/image"; import { useState } from "react"; import { useAuthState } from "@/stores/auth/auth-context"; @@ -48,10 +49,13 @@ export function AuthEmailPanel({ {mode === "login" ? ( <>
- Cozsweet ) : ( diff --git a/src/app/auth/components/auth-facebook-panel.tsx b/src/app/auth/components/auth-facebook-panel.tsx index 9d63528a..a5199df8 100644 --- a/src/app/auth/components/auth-facebook-panel.tsx +++ b/src/app/auth/components/auth-facebook-panel.tsx @@ -11,6 +11,7 @@ * - "Other Sign-in Options" 链接打开底部弹层 * - 弹层 Email 按钮 → 触发 `onSwitchToEmail`(父级派发 `AuthPanelModeChanged`) */ +import Image from "next/image"; import { useState } from "react"; import { AuthPlatform } from "@/lib/auth/nextauth"; @@ -56,10 +57,13 @@ export function AuthFacebookPanel({ onSwitchToEmail }: AuthFacebookPanelProps) { return (
- Cozsweet
diff --git a/src/app/chat/components/browser-hint-overlay.tsx b/src/app/chat/components/browser-hint-overlay.tsx index 28d7b9b6..c84b046d 100644 --- a/src/app/chat/components/browser-hint-overlay.tsx +++ b/src/app/chat/components/browser-hint-overlay.tsx @@ -12,7 +12,7 @@ * - 生产环境仅在应用内浏览器中显示 * - 视觉:右上角 👆 + 模糊背景气泡 */ -import { useEffect, useState } from "react"; +import { useState } from "react"; import styles from "./browser-hint-overlay.module.css"; @@ -36,11 +36,8 @@ export function BrowserHintOverlay({ text = DEFAULT_TEXT, forceShow = false, }: BrowserHintOverlayProps) { - const [isInAppBrowser, setIsInAppBrowser] = useState(false); - - useEffect(() => { - setIsInAppBrowser(detectInAppBrowser()); - }, []); + // lazy initializer:首次渲染即计算(避免 useEffect 中调 setState 的 lint 错) + const [isInAppBrowser] = useState(detectInAppBrowser); // 开发模式或应用内浏览器才显示 const shouldShow = forceShow || isInAppBrowser; diff --git a/src/app/chat/components/chat-area.tsx b/src/app/chat/components/chat-area.tsx index 530d1e21..adc37372 100644 --- a/src/app/chat/components/chat-area.tsx +++ b/src/app/chat/components/chat-area.tsx @@ -75,11 +75,11 @@ function renderMessagesWithDateHeaders(messages: readonly UiMessage[]) { ) : ( + key={item.key} + content={item.message.content} + imageUrl={item.message.imageUrl} + isFromAI={item.message.isFromAI} + /> ), ); } @@ -87,7 +87,7 @@ function renderMessagesWithDateHeaders(messages: readonly UiMessage[]) { function AiDisclosure() { return (
- You're chatting with an AI companion. + {'You' + String.fromCharCode(39) + 're chatting with an AI companion.'}
); } @@ -101,5 +101,3 @@ function EmptyState({ isGuest }: { isGuest: boolean }) {
); } - - diff --git a/src/app/chat/components/fullscreen-image-viewer.tsx b/src/app/chat/components/fullscreen-image-viewer.tsx index 7d51141f..1de1a77c 100644 --- a/src/app/chat/components/fullscreen-image-viewer.tsx +++ b/src/app/chat/components/fullscreen-image-viewer.tsx @@ -9,6 +9,7 @@ * - 点击空白 / 下滑手势 → 关闭 * - 双指缩放(CSS `touch-action: pinch-zoom`) */ +import Image from "next/image"; import { useEffect } from "react"; import styles from "./fullscreen-image-viewer.module.css"; @@ -34,10 +35,12 @@ export function FullscreenImageViewer({ imageUrl, onClose }: FullscreenImageView role="dialog" aria-label="Fullscreen image" > - {/* eslint-disable-next-line @next/next/no-img-element */} - { (e.currentTarget as HTMLImageElement).style.display = "none"; (e.currentTarget.parentElement as HTMLElement).innerHTML = diff --git a/src/app/chat/components/image-bubble.tsx b/src/app/chat/components/image-bubble.tsx index 291dab7f..d735b417 100644 --- a/src/app/chat/components/image-bubble.tsx +++ b/src/app/chat/components/image-bubble.tsx @@ -9,6 +9,7 @@ * - 点击 → 打开全屏查看器 * - 错误占位符(broken image icon) */ +import Image from "next/image"; import { useState } from "react"; import { FullscreenImageViewer } from "./fullscreen-image-viewer"; @@ -42,11 +43,12 @@ export function ImageBubble({ imageUrl }: ImageBubbleProps) { {error ? (
🖼
) : ( - // eslint-disable-next-line @next/next/no-img-element - setError(true)} /> )} diff --git a/src/app/chat/components/message-avatar.tsx b/src/app/chat/components/message-avatar.tsx index 8c4ff501..68abf8c1 100644 --- a/src/app/chat/components/message-avatar.tsx +++ b/src/app/chat/components/message-avatar.tsx @@ -36,8 +36,7 @@ export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) { if (userAvatarUrl && userAvatarUrl.length > 0) { return (
- {/* eslint-disable-next-line @next/next/no-img-element */} - +
); } diff --git a/src/app/sidebar/components/sidebar-screen.tsx b/src/app/sidebar/components/sidebar-screen.tsx index 945a9798..b43d3d52 100644 --- a/src/app/sidebar/components/sidebar-screen.tsx +++ b/src/app/sidebar/components/sidebar-screen.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect } from "react"; +import Image from "next/image"; import Link from "next/link"; import { MobileShell } from "@/app/_components/core/mobile-shell"; @@ -35,8 +36,7 @@ export function SidebarScreen() {
{user.avatarUrl ? ( - // eslint-disable-next-line @next/next/no-img-element - + ) : ( {(user.currentUser?.username ?? "?").charAt(0).toUpperCase()} diff --git a/src/stores/auth/machine/auth-context.tsx b/src/stores/auth/auth-context.tsx similarity index 100% rename from src/stores/auth/machine/auth-context.tsx rename to src/stores/auth/auth-context.tsx diff --git a/src/stores/auth/machine/auth-events.ts b/src/stores/auth/auth-events.ts similarity index 100% rename from src/stores/auth/machine/auth-events.ts rename to src/stores/auth/auth-events.ts diff --git a/src/stores/auth/machine/auth-machine.ts b/src/stores/auth/auth-machine.ts similarity index 97% rename from src/stores/auth/machine/auth-machine.ts rename to src/stores/auth/auth-machine.ts index 1e494795..55789d25 100644 --- a/src/stores/auth/machine/auth-machine.ts +++ b/src/stores/auth/auth-machine.ts @@ -14,12 +14,12 @@ import { authRepository } from "@/data/repositories/auth_repository"; import { AuthStorage } from "@/data/storage/auth/auth_storage"; import { Result } from "@/utils/result"; -import { AuthState, initialState } from "../auth-state"; +import { AuthState, initialState } from "./auth-state"; import type { AuthEvent } from "./auth-events"; // 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变 -export type { AuthState } from "../auth-state"; -export { initialState } from "../auth-state"; +export type { AuthState } from "./auth-state"; +export { initialState } from "./auth-state"; export type { AuthEvent } from "./auth-events"; // ============================================================ diff --git a/src/stores/auth/auth-types.ts b/src/stores/auth/auth-types.ts index 9a42c424..998f3b13 100644 --- a/src/stores/auth/auth-types.ts +++ b/src/stores/auth/auth-types.ts @@ -7,4 +7,4 @@ * - Context 形状与初始值由 `auth-context.ts` 导出 * - `AuthState` 由 `auth-context.tsx` 导出(保持原 API 兼容) */ -export type { AuthEvent } from "./machine/auth-events"; +export type { AuthEvent } from "./auth-events"; diff --git a/src/stores/auth/index.ts b/src/stores/auth/index.ts index baa0c844..b9a6999d 100644 --- a/src/stores/auth/index.ts +++ b/src/stores/auth/index.ts @@ -8,6 +8,6 @@ */ export * from "./auth-state"; export * from "./auth-context"; -export * from "./machine/auth-events"; -export * from "./machine/auth-machine"; +export * from "./auth-events"; +export * from "./auth-machine"; export * from "./auth-types"; diff --git a/src/stores/chat/chat-context.tsx b/src/stores/chat/chat-context.tsx index 2efe40ed..2e0e6557 100644 --- a/src/stores/chat/chat-context.tsx +++ b/src/stores/chat/chat-context.tsx @@ -20,8 +20,8 @@ import { } from "react"; import { useMachine } from "@xstate/react"; -import { chatMachine } from "./machine/chat-machine"; -import type { ChatEvent, ChatState as MachineContext } from "./machine/chat-machine"; +import { chatMachine } from "./chat-machine"; +import type { ChatEvent, ChatState as MachineContext } from "./chat-machine"; /** * 对外暴露的 State 形状(保持与原 ChatState 兼容) @@ -46,7 +46,7 @@ export interface ChatProviderProps { } export function ChatProvider({ children }: ChatProviderProps) { - const [state, send] = useMachine(chatMachine); + const [state, send] = useMachine(chatMachine, {}); // 映射 XState 状态机快照 → 原 ChatState 形状 const chatState = useMemo( diff --git a/src/stores/chat/machine/chat-events.ts b/src/stores/chat/chat-events.ts similarity index 100% rename from src/stores/chat/machine/chat-events.ts rename to src/stores/chat/chat-events.ts diff --git a/src/stores/chat/machine/chat-machine.ts b/src/stores/chat/chat-machine.ts similarity index 98% rename from src/stores/chat/machine/chat-machine.ts rename to src/stores/chat/chat-machine.ts index 5efae4c3..6ed38a8a 100644 --- a/src/stores/chat/machine/chat-machine.ts +++ b/src/stores/chat/chat-machine.ts @@ -24,7 +24,7 @@ import { formatDate } from "@/utils/date"; import { Result } from "@/utils/result"; import { ChatState, initialState } from "./chat-state"; -import type { ChatEvent } from "./machine/chat-events"; +import type { ChatEvent } from "./chat-events"; /** 游客配额阈值(与 Dart `GuestChatQuota` 保持一致) */ export const GuestChatQuota = { @@ -35,7 +35,7 @@ export const GuestChatQuota = { // 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变 export type { ChatState } from "./chat-state"; export { initialState } from "./chat-state"; -export type { ChatEvent } from "./machine/chat-events"; +export type { ChatEvent } from "./chat-events"; // ============================================================ // Helpers diff --git a/src/stores/chat/machine/chat-state.ts b/src/stores/chat/chat-state.ts similarity index 100% rename from src/stores/chat/machine/chat-state.ts rename to src/stores/chat/chat-state.ts diff --git a/src/stores/chat/chat-types.ts b/src/stores/chat/chat-types.ts index b4319e57..7d67d910 100644 --- a/src/stores/chat/chat-types.ts +++ b/src/stores/chat/chat-types.ts @@ -8,5 +8,5 @@ * - `ChatState` 由 `chat-context.tsx` 导出(保持原 API 兼容) * - `GuestChatQuota` 仍由 `chat-machine.ts` 导出(业务常量,与上下文配对紧密) */ -export type { ChatEvent } from "./machine/chat-events"; +export type { ChatEvent } from "./chat-events"; export { GuestChatQuota } from "./chat-machine"; diff --git a/src/stores/chat/index.ts b/src/stores/chat/index.ts index d9b4c66f..fe7201eb 100644 --- a/src/stores/chat/index.ts +++ b/src/stores/chat/index.ts @@ -11,8 +11,8 @@ * 注:chat-side-effects.ts 暂未删除(其中 WebSocket 长生命周期逻辑 * 待下一轮迁移到 fromCallback actor)。 */ -export * from "./machine/chat-state"; +export * from "./chat-state"; export * from "./chat-context"; -export * from "./machine/chat-events"; +export * from "./chat-events"; export * from "./chat-types"; export { chatMachine } from "./chat-machine"; diff --git a/src/stores/sidebar/index.ts b/src/stores/sidebar/index.ts index 9d43e4b3..aec50329 100644 --- a/src/stores/sidebar/index.ts +++ b/src/stores/sidebar/index.ts @@ -12,4 +12,4 @@ export * from "./sidebar-state"; export * from "./sidebar-context"; export * from "./sidebar-events"; export * from "./sidebar-types"; -export { sidebarMachine } from "./machine/sidebar-machine"; +export { sidebarMachine } from "./sidebar-machine"; diff --git a/src/stores/sidebar/sidebar-context.tsx b/src/stores/sidebar/sidebar-context.tsx index 1b02bc31..cb98a7ba 100644 --- a/src/stores/sidebar/sidebar-context.tsx +++ b/src/stores/sidebar/sidebar-context.tsx @@ -17,11 +17,11 @@ import { } from "react"; import { useMachine } from "@xstate/react"; -import { sidebarMachine } from "./machine/sidebar-machine"; +import { sidebarMachine } from "./sidebar-machine"; import type { SidebarState as MachineContext, SidebarEvent, -} from "./machine/sidebar-machine"; +} from "./sidebar-machine"; /** * 对外暴露的 State 形状(保持与原 SidebarState 兼容) @@ -37,7 +37,7 @@ const SidebarStateCtx = createContext(null); const SidebarDispatchCtx = createContext | null>(null); export function SidebarProvider({ children }: { children: ReactNode }) { - const [state, send] = useMachine(sidebarMachine); + const [state, send] = useMachine(sidebarMachine, {}); // 映射 XState 状态机快照 → 原 SidebarState 形状 const sidebarState = useMemo( diff --git a/src/stores/sidebar/machine/sidebar-events.ts b/src/stores/sidebar/sidebar-events.ts similarity index 100% rename from src/stores/sidebar/machine/sidebar-events.ts rename to src/stores/sidebar/sidebar-events.ts diff --git a/src/stores/sidebar/machine/sidebar-machine.ts b/src/stores/sidebar/sidebar-machine.ts similarity index 93% rename from src/stores/sidebar/machine/sidebar-machine.ts rename to src/stores/sidebar/sidebar-machine.ts index f6e747b1..89a478e9 100644 --- a/src/stores/sidebar/machine/sidebar-machine.ts +++ b/src/stores/sidebar/sidebar-machine.ts @@ -11,7 +11,7 @@ */ import { setup, assign } from "xstate"; -import { SidebarState, initialState } from "../sidebar-state"; +import { SidebarState, initialState } from "./sidebar-state"; import type { SidebarEvent } from "./sidebar-events"; /** @@ -37,8 +37,8 @@ export const BottomNavItem = { export type BottomNavItem = (typeof BottomNavItem)[keyof typeof BottomNavItem]; // 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变 -export type { SidebarState } from "../sidebar-state"; -export { initialState } from "../sidebar-state"; +export type { SidebarState } from "./sidebar-state"; +export { initialState } from "./sidebar-state"; export type { SidebarEvent } from "./sidebar-events"; // ============================================================ diff --git a/src/stores/sidebar/machine/sidebar-state.ts b/src/stores/sidebar/sidebar-state.ts similarity index 87% rename from src/stores/sidebar/machine/sidebar-state.ts rename to src/stores/sidebar/sidebar-state.ts index dd53b815..3ae0e1ba 100644 --- a/src/stores/sidebar/machine/sidebar-state.ts +++ b/src/stores/sidebar/sidebar-state.ts @@ -3,7 +3,7 @@ * * SidebarPage / BottomNavItem 仍由 sidebar-machine.ts 导出(事件/上下文均需引用)。 */ -import { SidebarPage, BottomNavItem } from "./machine/sidebar-machine"; +import { SidebarPage, BottomNavItem } from "./sidebar-machine"; export interface SidebarState { currentPage: SidebarPage; diff --git a/src/stores/sidebar/sidebar-types.ts b/src/stores/sidebar/sidebar-types.ts index b9e00e7a..a81b4c77 100644 --- a/src/stores/sidebar/sidebar-types.ts +++ b/src/stores/sidebar/sidebar-types.ts @@ -8,5 +8,5 @@ * - Context 形状由 `sidebar-context.ts` 导出 * - SidebarState 由 `sidebar-context.tsx` 导出(保持原 API 兼容) */ -export { SidebarPage, BottomNavItem } from "./machine/sidebar-machine"; +export { SidebarPage, BottomNavItem } from "./sidebar-machine"; export type { SidebarEvent } from "./sidebar-events"; diff --git a/src/stores/user/index.ts b/src/stores/user/index.ts index 07faba26..a3a041d1 100644 --- a/src/stores/user/index.ts +++ b/src/stores/user/index.ts @@ -8,8 +8,8 @@ * - Context 形状与初始值位于 user-context.ts * - 事件联合位于 user-events.ts */ -export * from "./machine/user-state"; +export * from "./user-state"; export * from "./user-context"; -export * from "./machine/user-events"; +export * from "./user-events"; export * from "./user-types"; export { userMachine } from "./user-machine"; diff --git a/src/stores/user/machine/user-events.ts b/src/stores/user/user-events.ts similarity index 100% rename from src/stores/user/machine/user-events.ts rename to src/stores/user/user-events.ts diff --git a/src/stores/user/machine/user-machine.ts b/src/stores/user/user-machine.ts similarity index 94% rename from src/stores/user/machine/user-machine.ts rename to src/stores/user/user-machine.ts index 7b5afade..b410da47 100644 --- a/src/stores/user/machine/user-machine.ts +++ b/src/stores/user/user-machine.ts @@ -17,13 +17,13 @@ import { authRepository } from "@/data/repositories/auth_repository"; import { UserStorage } from "@/data/storage/user/user_storage"; import { Result } from "@/utils/result"; -import { UserState, initialState } from "./machine/user-state"; -import type { UserEvent } from "./machine/user-events"; +import { UserState, initialState } from "./user-state"; +import type { UserEvent } from "./user-events"; // 重新导出 State / Event 类型,保持 machine 文件的公共 API 不变 -export type { UserState } from "./machine/user-state"; -export { initialState } from "./machine/user-state"; -export type { UserEvent } from "./machine/user-events"; +export type { UserState } from "./user-state"; +export { initialState } from "./user-state"; +export type { UserEvent } from "./user-events"; // ============================================================ // Helpers diff --git a/src/stores/user/machine/user-state.ts b/src/stores/user/user-state.ts similarity index 100% rename from src/stores/user/machine/user-state.ts rename to src/stores/user/user-state.ts diff --git a/src/stores/user/user-types.ts b/src/stores/user/user-types.ts index 3d4c47b5..738d2605 100644 --- a/src/stores/user/user-types.ts +++ b/src/stores/user/user-types.ts @@ -7,4 +7,4 @@ * - Context 形状与初始值由 `user-context.ts` 导出 * - `UserState` 由 `user-context.tsx` 导出(保持原 API 兼容) */ -export type { UserEvent } from "./machine/user-events"; +export type { UserEvent } from "./user-events";