nomadweb/frontend/src/components/ContentSubmitClient.tsx
eric bdf0516197 Fix dating match gate and expand seed data for usable demos.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-04 06:18:25 -05:00

162 lines
5.0 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { api } from "@/lib/api";
import { useAuth } from "@/lib/auth";
import { useToast } from "@/lib/toast";
import { useI18n } from "@/lib/i18n";
import RingNext from "@/components/RingNext";
import { useRingSteps } from "@/lib/rings";
import { clearDraft, loadDraft, saveDraft } from "@/lib/localDraft";
const DRAFT_KEY = "nomadro-submit-draft";
type SubmitDraft = { title: string; url: string; notes: string; contentType: string };
export default function ContentSubmitClient() {
const { token } = useAuth();
const router = useRouter();
const { toast } = useToast();
const { t } = useI18n();
const rings = useRingSteps();
const [title, setTitle] = useState("");
const [url, setUrl] = useState("");
const [notes, setNotes] = useState("");
const [contentType, setContentType] = useState("article");
const [busy, setBusy] = useState(false);
const [done, setDone] = useState(false);
const [ready, setReady] = useState(false);
useEffect(() => {
const draft = loadDraft<SubmitDraft>(DRAFT_KEY);
if (draft) {
setTitle(draft.title || "");
setUrl(draft.url || "");
setNotes(draft.notes || "");
setContentType(draft.contentType || "article");
}
setReady(true);
}, []);
useEffect(() => {
if (!ready) return;
const id = window.setTimeout(() => {
if (!title && !url && !notes) {
clearDraft(DRAFT_KEY);
return;
}
saveDraft(DRAFT_KEY, { title, url, notes, contentType });
}, 400);
return () => window.clearTimeout(id);
}, [title, url, notes, contentType, ready]);
const submit = async () => {
if (!token) {
router.push("/login?next=/submit");
return;
}
if (!title.trim() || title.trim().length < 4) {
toast(t.submit.needTitle, "info");
return;
}
if ((contentType === "article" || contentType === "video") && !/^https?:\/\/.+/.test(url.trim())) {
toast(t.submit.needUrl, "info");
return;
}
setBusy(true);
try {
await api.submitContent(
{
title: title.trim(),
url: url.trim(),
notes: notes.trim(),
content_type: contentType,
name: "",
},
token
);
clearDraft(DRAFT_KEY);
toast(t.submit.ok, "success", {
href: "/community",
label: t.submit.goCommunity,
});
setTitle("");
setUrl("");
setNotes("");
setDone(true);
} catch {
toast(t.submit.fail, "error");
} finally {
setBusy(false);
}
};
return (
<div className="community-page">
<div className="container">
<nav className="detail-nav">
<Link href="/">{t.common.backHome}</Link>
</nav>
<div className="section-header reveal">
<span className="section-tag">{t.submit.tag}</span>
<h1>{t.submit.title}</h1>
<p>{t.submit.subtitle}</p>
{!done && <p className="section-desc">{t.submit.draftHint}</p>}
</div>
{done ? (
<div className="dating-gate reveal">
<h2>{t.submit.thanksTitle}</h2>
<p>{t.submit.thanksDesc}</p>
<div className="dating-empty-actions">
<button type="button" className="btn btn-primary" onClick={() => setDone(false)}>
{t.submit.writeAgain}
</button>
<Link href="/community" className="btn">
{t.submit.goCommunity}
</Link>
</div>
</div>
) : (
<div className="community-compose reveal">
<select value={contentType} onChange={(e) => setContentType(e.target.value)}>
<option value="article">{t.submit.article}</option>
<option value="video">{t.submit.video}</option>
<option value="ebook">{t.submit.ebook}</option>
</select>
<input
placeholder={t.submit.subject}
value={title}
onChange={(e) => setTitle(e.target.value)}
maxLength={120}
/>
<input
placeholder={t.submit.url}
value={url}
onChange={(e) => setUrl(e.target.value)}
maxLength={500}
/>
<textarea
rows={4}
placeholder={t.submit.notes}
value={notes}
onChange={(e) => setNotes(e.target.value)}
maxLength={2000}
/>
<button type="button" className="btn btn-primary" disabled={busy} onClick={() => void submit()}>
{busy ? t.submit.submitting : t.submit.send}
</button>
{!token && (
<p className="section-desc">
<Link href="/login?next=/submit">{t.nav.login}</Link> · {t.gigs.loginFirst}
</p>
)}
</div>
)}
<RingNext steps={rings.afterGigs} />
</div>
</div>
);
}