162 lines
5.0 KiB
TypeScript
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>
|
|
);
|
|
}
|