83 lines
2.5 KiB
TypeScript
83 lines
2.5 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { Link, useParams } from 'react-router-dom';
|
|
import { api } from '../api/client';
|
|
import { useAuth } from '../lib/auth';
|
|
import { Button } from '../components/ui';
|
|
import { IconBack } from '../components/icons';
|
|
import type { Thread } from '../types';
|
|
|
|
export function ThreadPage() {
|
|
const { id } = useParams();
|
|
const { user } = useAuth();
|
|
const [thread, setThread] = useState<Thread | null>(null);
|
|
const [body, setBody] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
if (id) api.thread(Number(id)).then(setThread).catch((e) => setError(String(e)));
|
|
}, [id]);
|
|
|
|
const reply = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
if (!id) return;
|
|
setError('');
|
|
try {
|
|
const updated = await api.addReply(Number(id), body);
|
|
setThread(updated);
|
|
setBody('');
|
|
} catch (err) {
|
|
setError(err instanceof Error ? err.message : '回复失败');
|
|
}
|
|
};
|
|
|
|
if (error) return <p className="error">{error}</p>;
|
|
if (!thread) return <p className="muted">加载中…</p>;
|
|
|
|
const fmt = (s: string) => new Date(s + 'Z').toLocaleString('zh-CN');
|
|
|
|
return (
|
|
<div className="page thread-page">
|
|
<Link to={thread.model_id ? `/models/${thread.model_id}` : '/community'} className="back">
|
|
<IconBack size={14} /> 返回
|
|
</Link>
|
|
<h1>{thread.title}</h1>
|
|
<div className="post op">
|
|
<div className="post-head">
|
|
<b>{thread.author_name}</b>
|
|
<span className="muted">{fmt(thread.created_at)}</span>
|
|
</div>
|
|
<p className="post-body">{thread.body}</p>
|
|
</div>
|
|
|
|
<h2>{thread.reply_count} 条回复</h2>
|
|
<div className="replies">
|
|
{thread.replies?.map((r) => (
|
|
<div className="post" key={r.id}>
|
|
<div className="post-head">
|
|
<b>{r.author_name}</b>
|
|
<span className="muted">{fmt(r.created_at)}</span>
|
|
</div>
|
|
<p className="post-body">{r.body}</p>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
{user ? (
|
|
<form className="reply-form" onSubmit={reply}>
|
|
<textarea
|
|
placeholder="写下你的回复…"
|
|
value={body}
|
|
onChange={(e) => setBody(e.target.value)}
|
|
rows={3}
|
|
required
|
|
/>
|
|
{error && <p className="error">{error}</p>}
|
|
<Button variant="primary" type="submit">回复</Button>
|
|
</form>
|
|
) : (
|
|
<p className="muted">登录后可参与讨论。</p>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|