Files
2026-03-15 04:25:45 -05:00

167 lines
5.5 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import Link from "next/link";
import { useCallback, useEffect, useState } from "react";
import { CommunityPageHero } from "../components/CommunityPageHero";
import { COMMUNITY_CHANGED_EVENT } from "../components/communityEvents";
import styles from "../components/memos.module.css";
type AttachmentItem = {
id: string;
url: string;
original_name: string;
created: string;
size: number;
};
function getStorage(key: string): string | null {
if (typeof window === "undefined") {
return null;
}
return localStorage.getItem(key);
}
function formatFileSize(size: number) {
if (size < 1024) {
return `${size} B`;
}
if (size < 1024 * 1024) {
return `${(size / 1024).toFixed(1)} KB`;
}
return `${(size / 1024 / 1024).toFixed(1)} MB`;
}
export default function AttachmentsPage() {
const [userId, setUserId] = useState<string | null>(null);
const [isVip, setIsVip] = useState(false);
const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const [items, setItems] = useState<AttachmentItem[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loading, setLoading] = useState(false);
useEffect(() => {
const uid = getStorage("userId") || getStorage("memosAccount");
const type = getStorage("paidType");
setUserId(uid || null);
setIsVip(type === "vip");
}, []);
const fetchAttachments = useCallback(
async (pageNum: number, append: boolean) => {
if (!userId || !isVip) {
return;
}
setLoading(true);
try {
const response = await fetch(
`/api/community/attachments?user_id=${encodeURIComponent(userId)}&page=${pageNum}&per_page=24`,
{ cache: "no-store" }
);
const data = await response.json().catch(() => null);
if (response.status === 403) {
setShowLoginPrompt(true);
return;
}
if (!response.ok) {
return;
}
const nextItems = Array.isArray(data?.items) ? (data.items as AttachmentItem[]) : [];
setItems((current) => (append ? [...current, ...nextItems] : nextItems));
setHasMore(Boolean(data?.next_page_token));
setPage(pageNum);
} finally {
setLoading(false);
}
},
[isVip, userId]
);
useEffect(() => {
void fetchAttachments(1, false);
}, [fetchAttachments]);
useEffect(() => {
const handler = () => {
void fetchAttachments(1, false);
};
window.addEventListener(COMMUNITY_CHANGED_EVENT, handler);
return () => window.removeEventListener(COMMUNITY_CHANGED_EVENT, handler);
}, [fetchAttachments]);
return (
<div className={styles.pageStack}>
<CommunityPageHero
eyebrow="Attachment Library"
title="图片和资料集中沉淀"
description="所有在动态里上传的图片都会自动汇总到这里,方便回看和二次引用。"
chips={["自动聚合", "适合资料沉淀", "支持快速回看"]}
actions={[{ href: "/community", label: "返回首页", secondary: true }]}
/>
{showLoginPrompt ? (
<section className={styles.vipBanner}>
<p></p>
<Link href="/" className={styles.ctaLink}>
</Link>
</section>
) : null}
{isVip && userId ? (
items.length ? (
<>
<section className={styles.attachmentGrid}>
{items.map((item) => (
<a key={item.id} href={item.url} target="_blank" rel="noreferrer" className={styles.attachmentCard}>
<img src={item.url} alt={item.original_name} className={styles.attachmentImage} />
<div className={styles.attachmentMeta}>
<span className={styles.attachmentName}>{item.original_name}</span>
<span className={styles.attachmentSubMeta}>
{formatFileSize(item.size)} · {new Date(item.created).toLocaleDateString("zh-CN")}
</span>
</div>
</a>
))}
</section>
{hasMore ? (
<button
type="button"
className={styles.loadMorePanelBtn}
onClick={() => void fetchAttachments(page + 1, true)}
disabled={loading}
>
{loading ? "加载中..." : "加载更多附件"}
</button>
) : null}
</>
) : (
<section className={styles.lockedPanel}>
<div className={styles.lockedPanelCopy}>
<span className={styles.lockedPanelEyebrow}>Attachment Library</span>
<h2 className={styles.lockedPanelTitle}></h2>
<p className={styles.lockedPanelDesc}></p>
</div>
</section>
)
) : (
<section className={styles.lockedPanel}>
<div className={styles.lockedPanelCopy}>
<span className={styles.lockedPanelEyebrow}>Members Only</span>
<h2 className={styles.lockedPanelTitle}></h2>
<p className={styles.lockedPanelDesc}></p>
</div>
<div className={styles.lockedPanelActions}>
<Link href="/" className={styles.heroPrimaryAction}>
</Link>
</div>
</section>
)}
</div>
);
}