This repository has been archived on 2026-09-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
confessions-of-grace/frontend/src/components/CommentSection.tsx
T
austin 0c5bbaaa01 Rewrite as a Spring Boot app on the Bennett platform
Restores the real backend lost with Supabase (posts, authors, comments, subscriptions, admin) in
Postgres, seeds the existing markdown posts, and rebuilds the site as a Vite/React SPA served by
Spring — keeping the original styling (serif + tan accent) and content.
2026-07-22 20:35:51 -05:00

79 lines
3.1 KiB
TypeScript

import { useState } from 'react';
import type { FormEvent } from 'react';
import { format } from 'date-fns';
import { getComments, postComment } from '../api';
import { useAsync } from '../lib/useAsync';
export default function CommentSection({ postId }: { postId: string }) {
const [reloadKey, setReloadKey] = useState(0);
const { data: comments, loading } = useAsync(() => getComments(postId), [postId, reloadKey]);
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [comment, setComment] = useState('');
const [state, setState] = useState<'idle' | 'busy' | 'error'>('idle');
async function onSubmit(e: FormEvent) {
e.preventDefault();
if (!name.trim() || !email.trim() || !comment.trim()) return;
setState('busy');
try {
await postComment({ postId, name: name.trim(), email: email.trim(), comment: comment.trim() });
setName(''); setEmail(''); setComment('');
setState('idle');
setReloadKey((k) => k + 1);
} catch {
setState('error');
}
}
const list = comments ?? [];
return (
<section className="mt-12">
<h2 className="mb-6 border-b border-primary-200 pb-2 text-2xl font-bold">
{list.length === 1 ? '1 Comment' : `${list.length} Comments`}
</h2>
{loading && <p className="text-primary-500">Loading comments</p>}
<ul className="mb-10 space-y-4">
{list.map((c) => (
<li key={c.id} className="card">
<div className="mb-1 flex items-center text-sm text-primary-500">
<span className="font-semibold text-primary-700">{c.name}</span>
{c.createdAt && (
<>
<span className="mx-2"></span>
<time dateTime={c.createdAt}>{format(new Date(c.createdAt), 'MMMM d, yyyy')}</time>
</>
)}
</div>
<p className="whitespace-pre-line text-primary-700">{c.body}</p>
</li>
))}
{!loading && list.length === 0 && (
<li className="text-primary-500">No comments yet be the first to share a thought.</li>
)}
</ul>
<div className="card">
<h3 className="mb-4 text-xl font-bold">Leave a comment</h3>
<form onSubmit={onSubmit} className="space-y-3">
<div className="grid grid-cols-1 gap-3 md:grid-cols-2">
<input className="field" placeholder="Name" value={name} onChange={(e) => setName(e.target.value)} required />
<input className="field" type="email" placeholder="Email (not published)" value={email}
onChange={(e) => setEmail(e.target.value)} required />
</div>
<textarea className="field min-h-32" placeholder="Your comment" value={comment}
onChange={(e) => setComment(e.target.value)} required />
<button type="submit" className="button" disabled={state === 'busy'}>
{state === 'busy' ? 'Posting…' : 'Post Comment'}
</button>
{state === 'error' && <p className="text-sm text-red-700">Something went wrong. Please try again.</p>}
</form>
</div>
</section>
);
}