158 lines
5.2 KiB
TypeScript
158 lines
5.2 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 {
|
|
clearCommunityDraft,
|
|
loadCommunityDraft,
|
|
saveCommunityDraft,
|
|
} from "@/lib/communityDraft";
|
|
import { inferMeetupCity, meetupCityHref } from "@/lib/meetupLinks";
|
|
import type { Destination } from "@/lib/types";
|
|
|
|
export default function CommunityNewClient() {
|
|
const { token } = useAuth();
|
|
const router = useRouter();
|
|
const { toast } = useToast();
|
|
const { t } = useI18n();
|
|
const rings = useRingSteps();
|
|
const [title, setTitle] = useState("");
|
|
const [content, setContent] = useState("");
|
|
const [category, setCategory] = useState("社区");
|
|
const [busy, setBusy] = useState(false);
|
|
const [draftReady, setDraftReady] = useState(false);
|
|
const [cities, setCities] = useState<Destination[]>([]);
|
|
|
|
useEffect(() => {
|
|
const draft = loadCommunityDraft();
|
|
if (draft) {
|
|
setTitle(draft.title || "");
|
|
setContent(draft.content || "");
|
|
setCategory(draft.category || "社区");
|
|
if (draft.title || draft.content) {
|
|
toast(t.community.draftRestored, "info");
|
|
}
|
|
}
|
|
setDraftReady(true);
|
|
api
|
|
.getDestinations()
|
|
.then(setCities)
|
|
.catch(() => setCities([]));
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (!draftReady || !token) return;
|
|
const id = window.setTimeout(() => {
|
|
if (!title.trim() && !content.trim()) {
|
|
clearCommunityDraft();
|
|
return;
|
|
}
|
|
saveCommunityDraft({ title, content, category });
|
|
}, 400);
|
|
return () => window.clearTimeout(id);
|
|
}, [title, content, category, draftReady, token]);
|
|
|
|
if (!token) {
|
|
return (
|
|
<div className="community-page">
|
|
<div className="container">
|
|
<nav className="detail-nav"><Link href="/community">← {t.community.back}</Link></nav>
|
|
<div className="dating-gate reveal">
|
|
<h2>{t.community.newTitle}</h2>
|
|
<p>{t.community.loginDesc}</p>
|
|
<div className="dating-empty-actions">
|
|
<Link href="/login?next=/community/new" className="btn btn-primary">{t.nav.login}</Link>
|
|
<Link href="/meetups" className="btn btn-ghost">{t.dating.meetAtEvents}</Link>
|
|
</div>
|
|
</div>
|
|
<RingNext steps={rings.afterCommunity()} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const submit = async () => {
|
|
if (!token) {
|
|
router.push("/login?next=/community/new");
|
|
return;
|
|
}
|
|
const tTitle = title.trim();
|
|
const body = content.trim();
|
|
if (!tTitle || body.length < 10) {
|
|
toast(t.community.draftNeed, "info");
|
|
return;
|
|
}
|
|
setBusy(true);
|
|
try {
|
|
const excerpt = body.slice(0, 1000);
|
|
const res = await api.createDiscussion(token, {
|
|
title: tTitle,
|
|
content: body,
|
|
excerpt,
|
|
category,
|
|
});
|
|
clearCommunityDraft();
|
|
const city = inferMeetupCity(
|
|
`${tTitle} ${body} ${category}`,
|
|
cities.map((d) => d.name)
|
|
);
|
|
toast(t.community.createOk, "success", {
|
|
href: city ? meetupCityHref(city) : "/meetups",
|
|
label: city ? t.common.cityMeetups : t.dating.meetAtEvents,
|
|
});
|
|
const id = res.discussion?.id;
|
|
router.push(id ? `/community/${id}` : "/community");
|
|
} catch {
|
|
toast(t.community.createFail, "error");
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="community-page">
|
|
<div className="container">
|
|
<nav className="detail-nav"><Link href="/community">← {t.community.back}</Link></nav>
|
|
<div className="section-header reveal">
|
|
<h1>{t.community.newTitle}</h1>
|
|
<p className="section-desc">{t.community.draftHint}</p>
|
|
</div>
|
|
<div className="join-card reveal">
|
|
<label>{t.community.newSubject}</label>
|
|
<input value={title} onChange={(e) => setTitle(e.target.value)} maxLength={120} />
|
|
<label>{t.community.newCategory}</label>
|
|
<select value={category} onChange={(e) => setCategory(e.target.value)}>
|
|
{(
|
|
[
|
|
["签证", t.community.catVisa],
|
|
["远程工作", t.community.catRemote],
|
|
["住宿", t.community.catHousing],
|
|
["安全", t.community.catSafety],
|
|
["社区", t.community.catCommunity],
|
|
] as const
|
|
).map(([value, label]) => (
|
|
<option key={value} value={value}>
|
|
{label}
|
|
</option>
|
|
))}
|
|
</select>
|
|
<label>{t.community.newBody}</label>
|
|
<textarea value={content} onChange={(e) => setContent(e.target.value)} rows={6} maxLength={4000} />
|
|
<button type="button" className="btn btn-primary" disabled={busy} onClick={() => void submit()}>
|
|
{busy ? t.meetups.hosting : t.community.newSubmit}
|
|
</button>
|
|
</div>
|
|
<RingNext steps={rings.afterCommunity()} />
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|