108 lines
5.3 KiB
TypeScript
108 lines
5.3 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import Link from "next/link";
|
|
import { apiFetch, type Job } from "@/lib/api";
|
|
import { GATE_META, type PlayreelJob } from "@/lib/playreel";
|
|
|
|
/* 무빙포스터 진입 — 두 갈래 (MOVING_POSTER_ENTRY_PLAN.md §1·§5) */
|
|
|
|
const ENTRIES = [
|
|
{
|
|
href: "/poster", icon: "IMG", title: "이미지로 시작하기",
|
|
desc: "포스터 한 장이 첫 프레임 그대로 살아나는 8~15초 무빙포스터",
|
|
flow: ["포스터 원본이 첫 프레임", "불꽃·물결·조명 등 요소만 움직임", "제목·일시 나레이션 + 음악"],
|
|
facts: [["필요한 것", "이미지 1장"], ["확인", "1회"], ["시간", "약 3분"], ["비용", "14크레딧"]],
|
|
cta: "포스터 올리기", hot: false, badge: null,
|
|
},
|
|
{
|
|
href: "/playreel", icon: "URL", title: "공연상품페이지로 시작하기",
|
|
desc: "상품페이지 주소 하나로 캐스팅·일정·줄거리까지 담긴 30초 예고편",
|
|
flow: ["포스터 무빙 훅 8초", "상세페이지 스크롤 (줄거리·캐스트·캐스팅 스케줄·할인)", "예매 안내 밴드 + QR"],
|
|
facts: [["필요한 것", "상품페이지 URL"], ["확인", "5회"], ["시간", "약 15분"], ["비용", "16크레딧"]],
|
|
cta: "주소 넣기", hot: true, badge: "상세페이지까지",
|
|
},
|
|
] as const;
|
|
|
|
type Recent = { id: string; name: string; href: string; kind: "이미지" | "상품페이지"; label: string; tone: string };
|
|
|
|
const F1_STATUS: Record<string, string> = {
|
|
queued: "대기 중", running: "만드는 중", awaiting_review: "검수 대기", failed: "실패", done: "완료",
|
|
};
|
|
|
|
function tone(status: string) {
|
|
return status === "failed" ? "#ff7a7a" : status === "done" ? "var(--color-mint)" : status === "awaiting_review" ? "#ffd27a" : "var(--color-text-gray-400)";
|
|
}
|
|
|
|
export default function HomePage() {
|
|
const [recent, setRecent] = useState<Recent[]>([]);
|
|
|
|
useEffect(() => {
|
|
// 두 갈래의 최근 작업을 합쳐 최신순 6개. 한쪽 API가 없어도(playreel 미구현) 다른 쪽은 보인다.
|
|
Promise.all([
|
|
apiFetch<Job[]>("/api/f1/jobs").catch(() => [] as Job[]),
|
|
apiFetch<PlayreelJob[]>("/api/playreel/jobs").catch(() => [] as PlayreelJob[]),
|
|
]).then(([f1, pr]) => {
|
|
const a: (Recent & { t: number })[] = [
|
|
...f1.map((j) => ({ id: j.id, name: j.name, href: `/poster/${j.id}`, kind: "이미지" as const, label: F1_STATUS[j.status] ?? j.status, tone: tone(j.status), t: j.created_at })),
|
|
...pr.map((j) => ({ id: j.id, name: j.name, href: `/playreel/${j.id}`, kind: "상품페이지" as const,
|
|
label: j.status === "awaiting_review" && j.gate ? `${GATE_META[j.gate].name} 대기` : (F1_STATUS[j.status] ?? j.status), tone: tone(j.status), t: j.created_at })),
|
|
];
|
|
setRecent(a.sort((x, y) => y.t - x.t).slice(0, 6));
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<div style={{ display: "flex", flexDirection: "column", alignItems: "center", paddingTop: "1.5rem" }}>
|
|
<h1 className="page-title">어떻게 시작할까요?</h1>
|
|
<p className="page-subtitle">가진 것에 따라 고르세요. 둘 다 9:16 세로 영상으로 완성됩니다.</p>
|
|
|
|
<div className="entry-stack">
|
|
{ENTRIES.map((e) => (
|
|
<div key={e.href} className={`card entry${e.hot ? " entry--hot" : ""}`}>
|
|
{e.badge && <span className="entry-badge">{e.badge}</span>}
|
|
<div className="entry-head">
|
|
<div className="entry-icon">{e.icon}</div>
|
|
<div>
|
|
<h2 className="entry-title">{e.title}</h2>
|
|
<p className="entry-desc">{e.desc}</p>
|
|
</div>
|
|
</div>
|
|
<div className="entry-flow">
|
|
{e.flow.map((f, i) => (
|
|
<span key={f} style={{ display: "contents" }}>
|
|
{i > 0 && <em>→</em>}
|
|
<span>{f}</span>
|
|
</span>
|
|
))}
|
|
</div>
|
|
<div className="entry-facts">
|
|
{e.facts.map(([k, v]) => (
|
|
<div key={k}><b>{k}</b><span>{v}</span></div>
|
|
))}
|
|
</div>
|
|
<Link href={e.href} className="btn-cta btn-lg" style={{ textDecoration: "none" }}>{e.cta}</Link>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{recent.length > 0 && (
|
|
<div style={{ width: "100%", maxWidth: 560, marginTop: "2.5rem" }}>
|
|
<p className="field-label" style={{ margin: "0 0 0.75rem" }}>최근 작업</p>
|
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: "0.75rem" }}>
|
|
{recent.map((j) => (
|
|
<Link key={j.href} href={j.href} style={{ textDecoration: "none", color: "inherit" }}>
|
|
<div className="card-inner" style={{ padding: "1rem" }}>
|
|
<span className="tag" style={{ height: 20, padding: "0 8px", fontSize: "var(--text-xs)" }}>{j.kind}</span>
|
|
<p style={{ margin: "0.5rem 0 0", fontSize: "var(--text-sm)", fontWeight: 700, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{j.name}</p>
|
|
<p style={{ margin: "0.35rem 0 0", fontSize: "var(--text-xs)", fontWeight: 600, color: j.tone }}>{j.label}</p>
|
|
</div>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|