"use client"; import { useCallback, useState } from "react"; import { toast } from "sonner"; import { api, AdminError, type AdminFeedback } from "@/lib/api"; import { usePagedList } from "@/lib/use-paged-list"; import { ListShell, EmptyRow } from "@/components/list-shell"; import { Table, THead, TR, TH, TD } from "@/components/ui/table"; import { Button } from "@/components/ui/button"; import { Modal } from "@/components/ui/modal"; import { Textarea } from "@/components/ui/input"; import { cn, fmtDate } from "@/lib/utils"; const STATUSES = ["all", "open", "resolved"] as const; type Status = (typeof STATUSES)[number]; export default function FeedbackPage() { const [status, setStatus] = useState("all"); const fetcher = useCallback( (limit: number, offset: number) => api.feedback(status, limit, offset), [status], ); const list = usePagedList(fetcher, [status]); const [reply, setReply] = useState(null); return ( {STATUSES.map((s) => ( ))} } > {list.items.length === 0 && !list.loading ? ( ) : ( list.items.map((f) => ( )) )}
From Message Status Received Actions
{f.author_handle ? ( <>
{f.author_name}
@{f.author_handle}
) : (
{f.contact_email ?? "Anonymous"}
)}
{f.category}
{f.message}
{f.reply_message ? (
Your reply: {f.reply_message}
) : null}
{fmtDate(f.created_at)}
setReply(null)} onDone={() => { setReply(null); list.refresh(); }} />
); } function StatusBadge({ f }: { f: AdminFeedback }) { const label = f.replied_at ? "Replied" : f.resolved_at ? "Resolved" : "Open"; const tone = f.resolved_at || f.replied_at ? "text-primary" : "text-muted-foreground"; return {label}; } function ReplyModal({ feedback, onClose, onDone, }: { feedback: AdminFeedback | null; onClose: () => void; onDone: () => void; }) { const [message, setMessage] = useState(""); const [busy, setBusy] = useState(false); async function send() { if (!feedback || message.trim() === "") return; setBusy(true); try { const { data } = (await api.replyFeedback(feedback.id, message.trim())) as { data: { replied: boolean; emailed: boolean }; }; toast.success( data?.emailed ? "Reply saved and emailed to the submitter." : "Reply saved (no contact email on file to send to).", ); setMessage(""); onDone(); } catch (err) { toast.error(err instanceof AdminError ? err.message : "Could not reply."); } finally { setBusy(false); } } return (
{feedback ? (
{feedback.category}
{feedback.message}
) : null}