feat: implement post existence check and detail page
This commit is contained in:
parent
55af0549e7
commit
b18cff8b1a
10 changed files with 646 additions and 43 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
||||
import Link from "next/link";
|
||||
import Header from "../../components/header";
|
||||
|
||||
type SourceType = "twitter" | "pixiv";
|
||||
|
|
@ -26,6 +27,22 @@ type Me = {
|
|||
role: "admin" | "writer" | "reader";
|
||||
};
|
||||
|
||||
type UploadApiResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
savedCount?: number;
|
||||
failedCount?: number;
|
||||
};
|
||||
|
||||
type ExistsApiResponse = {
|
||||
success: boolean;
|
||||
message: string;
|
||||
exists: boolean;
|
||||
source: SourceType | null;
|
||||
postId: string | null;
|
||||
documentId: string | null;
|
||||
};
|
||||
|
||||
function detectSource(url: string): SourceType | null {
|
||||
if (/^https?:\/\/(www\.)?(x\.com|twitter\.com|fxtwitter\.com|fixupx\.com|vxwitter\.com)\//.test(url)) {
|
||||
return "twitter";
|
||||
|
|
@ -59,6 +76,7 @@ export default function AddPage() {
|
|||
const [lastFetchedUrl, setLastFetchedUrl] = useState("");
|
||||
const [viewerRole, setViewerRole] = useState<Me["role"] | "guest">("guest");
|
||||
const [loadingRole, setLoadingRole] = useState(true);
|
||||
const [existingDetailId, setExistingDetailId] = useState<string | null>(null);
|
||||
|
||||
const selectedCount = useMemo(
|
||||
() => selected.filter(Boolean).length,
|
||||
|
|
@ -110,6 +128,7 @@ export default function AddPage() {
|
|||
setLastFetchedUrl("");
|
||||
setError(null);
|
||||
setSuccess(null);
|
||||
setExistingDetailId(null);
|
||||
}
|
||||
|
||||
async function fetchPreview(targetUrl?: string) {
|
||||
|
|
@ -125,8 +144,25 @@ export default function AddPage() {
|
|||
|
||||
setLoadingPreview(true);
|
||||
setSourceType(source);
|
||||
setExistingDetailId(null);
|
||||
|
||||
try {
|
||||
const existsResponse = await fetch(`/api/post/exists?url=${encodeURIComponent(trimmedUrl)}`, {
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
if (existsResponse.ok) {
|
||||
const existsData = (await existsResponse.json()) as ExistsApiResponse;
|
||||
if (existsData.exists) {
|
||||
setPreviewItems([]);
|
||||
setSelected([]);
|
||||
setLastFetchedUrl(trimmedUrl);
|
||||
setExistingDetailId(existsData.documentId);
|
||||
setSuccess(existsData.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (source === "twitter") {
|
||||
const response = await fetch(`/api/tweet/fetch?url=${encodeURIComponent(trimmedUrl)}`, {
|
||||
cache: "no-store",
|
||||
|
|
@ -255,17 +291,28 @@ export default function AddPage() {
|
|||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const responseText = await response.text();
|
||||
throw new Error(responseText || `업로드 실패: ${response.status}`);
|
||||
let data: UploadApiResponse | null = null;
|
||||
try {
|
||||
data = (await response.json()) as UploadApiResponse;
|
||||
} catch {
|
||||
data = null;
|
||||
}
|
||||
|
||||
const uploadedCount = selectedCount;
|
||||
setUrl("");
|
||||
setAuthor("");
|
||||
setTagsText("");
|
||||
resetPreview();
|
||||
setSuccess(`${uploadedCount}개 이미지 업로드를 요청했습니다.`);
|
||||
const message = data?.message || `업로드 실패: ${response.status}`;
|
||||
|
||||
if (!response.ok || !data?.success) {
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
const uploadedCount = data.savedCount ?? selectedCount;
|
||||
if (uploadedCount > 0) {
|
||||
setUrl("");
|
||||
setAuthor("");
|
||||
setTagsText("");
|
||||
resetPreview();
|
||||
}
|
||||
|
||||
setSuccess(message);
|
||||
} catch (submitError) {
|
||||
setError(submitError instanceof Error ? submitError.message : "업로드에 실패했습니다.");
|
||||
} finally {
|
||||
|
|
@ -351,7 +398,14 @@ export default function AddPage() {
|
|||
</div>
|
||||
|
||||
{error ? <p className="text-sm text-red-600">{error}</p> : null}
|
||||
{success ? <p className="text-sm text-emerald-600">{success}</p> : null}
|
||||
{success ? <p className="text-sm text-emerald-600">{success} {existingDetailId ? (
|
||||
<Link
|
||||
href={`/detail/${existingDetailId}`}
|
||||
className="underline"
|
||||
>
|
||||
상세 페이지로 이동
|
||||
</Link>
|
||||
) : null}</p> : null}
|
||||
|
||||
{loadingPreview ? (
|
||||
<div className="border-l-2 border-border/80 pl-3 text-xs text-foreground/65">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue