feat(chat): restore image viewer after payment

This commit is contained in:
2026-06-29 16:19:48 +08:00
parent 6273cd1bbe
commit 61796c732f
12 changed files with 297 additions and 63 deletions
+41 -44
View File
@@ -4,73 +4,70 @@
*
* 支持:
* - base64 data URI 解码(`data:image/png;base64,...`
* - 点击 → 打开全屏查看器
* - 点击 → 跳转动态路由打开全屏查看器
* - 错误占位符(broken image icon
*/
import Image from "next/image";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { FullscreenImageViewer } from "./fullscreen-image-viewer";
import { ROUTE_BUILDERS } from "@/router/routes";
import styles from "./image-bubble.module.css";
export interface ImageBubbleProps {
messageId?: string;
imageUrl: string; // base64 data URI 或 URL
imagePaywalled?: boolean;
onUnlockImagePaywall?: () => void;
}
export function ImageBubble({
messageId,
imageUrl,
imagePaywalled = false,
onUnlockImagePaywall,
}: ImageBubbleProps) {
const [open, setOpen] = useState(false);
const router = useRouter();
const [error, setError] = useState(false);
const src = decodeBase64Image(imageUrl);
const canOpen = Boolean(messageId);
const openImage = () => {
if (!messageId) return;
router.push(ROUTE_BUILDERS.chatImage(messageId));
};
return (
<>
<div
className={`${styles.bubble} ${
imagePaywalled ? styles.paywalled : ""
}`}
onClick={() => setOpen(true)}
role="button"
tabIndex={0}
onKeyDown={(e) => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
setOpen(true);
}
}}
aria-label="Open image in fullscreen"
>
{error ? (
<div className={styles.errorFallback}>🖼</div>
) : (
<Image
className={styles.image}
src={src}
alt=""
width={240}
height={240}
onError={() => setError(true)}
/>
)}
{!error && imagePaywalled ? (
<div className={styles.paywallOverlay} aria-hidden="true" />
) : null}
</div>
{open && (
<FullscreenImageViewer
imageUrl={imageUrl}
imagePaywalled={imagePaywalled}
onUnlockImagePaywall={onUnlockImagePaywall}
onClose={() => setOpen(false)}
<div
className={`${styles.bubble} ${imagePaywalled ? styles.paywalled : ""}`}
onClick={openImage}
role={canOpen ? "button" : undefined}
tabIndex={canOpen ? 0 : undefined}
onKeyDown={(e) => {
if (!canOpen) return;
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
openImage();
}
}}
aria-label={canOpen ? "Open image in fullscreen" : undefined}
>
{error ? (
<div className={styles.errorFallback}>🖼</div>
) : (
<Image
className={styles.image}
src={src}
alt=""
width={240}
height={240}
onError={() => setError(true)}
/>
)}
</>
{!error && imagePaywalled ? (
<div className={styles.paywallOverlay} aria-hidden="true" />
) : null}
</div>
);
}