nomadweb/frontend/src/components/DiscussionDetailClient.tsx
eric ff7b721acb feat: NomadCNA community layer, social matching, payments, and ebook
Port meetups, discussions, gigs, dating/chat, VIP pay (ZPay/XorPay), MiroTalk live, digital academy, and ebook reader. Add persistent community_store, git-first deploy docs, and env template for production secrets.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-30 09:38:13 -05:00

104 lines
3.5 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { 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";
export default function DiscussionDetailClient({ discussionId, initial }: { discussionId: string; initial: import("@/lib/types").DiscussionDetail }) {
const { user, token } = useAuth();
const { toast } = useToast();
const { t } = useI18n();
const router = useRouter();
const [discussion, setDiscussion] = useState(initial);
const [reply, setReply] = useState("");
const [likes, setLikes] = useState(discussion.like_count);
const [loading, setLoading] = useState(false);
const refresh = async () => {
const d = await api.getDiscussion(discussionId);
setDiscussion(d);
setLikes(d.like_count);
};
const submitReply = async () => {
if (!token || !reply.trim()) return;
setLoading(true);
try {
await api.postDiscussionReply(token, discussionId, reply.trim());
setReply("");
await refresh();
toast(t.community.replyOk, "success");
} catch {
toast(t.community.replyFail, "error");
} finally {
setLoading(false);
}
};
const toggleLike = async () => {
if (!token) {
router.push(`/login?next=/community/${discussionId}`);
return;
}
try {
const res = await api.likeDiscussion(token, discussionId);
setLikes(res.like_count);
} catch { /* ignore */ }
};
return (
<div className="community-detail-page">
<div className="container">
<nav className="detail-nav">
<Link href="/community">← {t.community.back}</Link>
</nav>
<article className="community-detail reveal">
<header>
<span className="community-detail-cat">{discussion.category}</span>
<h1>{discussion.title}</h1>
<p className="community-meta">
{discussion.author_emoji} {discussion.author} · {discussion.created_at}
</p>
</header>
<div className="community-detail-body">
<p>{discussion.excerpt}</p>
</div>
<div className="community-detail-stats">
<span>💬 {discussion.reply_count} {t.community.replies}</span>
<button type="button" className="btn btn-sm" onClick={toggleLike}>❤️ {likes}</button>
</div>
</article>
<section className="community-replies reveal">
<h2>{t.community.replies} ({discussion.replies.length})</h2>
{discussion.replies.map((r) => (
<div key={r.id} className="community-reply">
<div className="community-reply-head">
<span>{r.author_emoji} {r.author}</span>
<time>{r.created_at}</time>
</div>
<p>{r.content}</p>
</div>
))}
</section>
{user && token ? (
<div className="community-compose reveal">
<textarea value={reply} onChange={(e) => setReply(e.target.value)} rows={3} placeholder={t.community.replyPlaceholder} />
<button type="button" className="btn btn-primary" disabled={loading} onClick={submitReply}>{t.community.replySend}</button>
</div>
) : (
<div className="community-reply-hint reveal">
<Link href={`/login?next=/community/${discussionId}`} className="btn btn-primary">{t.nav.login}</Link>
</div>
)}
</div>
</div>
);
}