refactor: migrate img tags to next/image and fix hook usage
This commit is contained in:
@@ -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" ? (
|
||||
<>
|
||||
<div className={styles.spacer24} />
|
||||
<img
|
||||
<Image
|
||||
src="/images/auth/ic-logo-login.png"
|
||||
alt="Cozsweet"
|
||||
width={120}
|
||||
height={120}
|
||||
className={styles.logo}
|
||||
priority
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
|
||||
@@ -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 (
|
||||
<div className={styles.panel}>
|
||||
<div className={styles.spacer} />
|
||||
<img
|
||||
<Image
|
||||
src="/images/auth/ic-logo-login.png"
|
||||
alt="Cozsweet"
|
||||
width={120}
|
||||
height={120}
|
||||
className={styles.logo}
|
||||
priority
|
||||
/>
|
||||
<div className={styles.spacer} />
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -75,11 +75,11 @@ function renderMessagesWithDateHeaders(messages: readonly UiMessage[]) {
|
||||
<DateHeader key={item.key} date={item.date} />
|
||||
) : (
|
||||
<MessageBubble
|
||||
key={item.key}
|
||||
content={item.message.content}
|
||||
imageUrl={item.message.imageUrl}
|
||||
isFromAI={item.message.isFromAI}
|
||||
/>
|
||||
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 (
|
||||
<div className={styles.aiDisclosure}>
|
||||
You're chatting with an AI companion.
|
||||
{'You' + String.fromCharCode(39) + 're chatting with an AI companion.'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -101,5 +101,3 @@ function EmptyState({ isGuest }: { isGuest: boolean }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 */}
|
||||
<img
|
||||
<Image
|
||||
src={imageUrl.startsWith("data:") || imageUrl.startsWith("http") ? imageUrl : `data:image/png;base64,${imageUrl}`}
|
||||
alt=""
|
||||
width={800}
|
||||
height={800}
|
||||
className={styles.viewerImage}
|
||||
onError={(e) => {
|
||||
(e.currentTarget as HTMLImageElement).style.display = "none";
|
||||
(e.currentTarget.parentElement as HTMLElement).innerHTML =
|
||||
|
||||
@@ -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 ? (
|
||||
<div className={styles.errorFallback}>🖼</div>
|
||||
) : (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img
|
||||
<Image
|
||||
className={styles.image}
|
||||
src={src}
|
||||
alt=""
|
||||
width={240}
|
||||
height={240}
|
||||
onError={() => setError(true)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -36,8 +36,7 @@ export function MessageAvatar({ isFromAI, userAvatarUrl }: MessageAvatarProps) {
|
||||
if (userAvatarUrl && userAvatarUrl.length > 0) {
|
||||
return (
|
||||
<div className={styles.avatar} aria-label="User avatar">
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img src={userAvatarUrl} alt="" />
|
||||
<Image src={userAvatarUrl} alt="" width={43} height={43} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
<section className={styles.profileCard}>
|
||||
<div className={styles.avatar}>
|
||||
{user.avatarUrl ? (
|
||||
// eslint-disable-next-line @next/next/no-img-element
|
||||
<img src={user.avatarUrl} alt="" />
|
||||
<Image src={user.avatarUrl} alt="" width={64} height={64} />
|
||||
) : (
|
||||
<span className={styles.avatarInitial}>
|
||||
{(user.currentUser?.username ?? "?").charAt(0).toUpperCase()}
|
||||
|
||||
Reference in New Issue
Block a user