'use client' import { useEffect, useState } from 'react' import { useAuth } from '@/context/AuthContext' type FeedbackSubmission = { id: string category: 'BUG' | 'FEATURE_REQUEST' | 'GENERAL' message: string email: string | null // ipAddress is returned by the API but not displayed in the table (privacy/noise) reviewed: boolean createdAt: string } const CATEGORY_LABELS: Record = { BUG: 'Bug', FEATURE_REQUEST: 'Feature Request', GENERAL: 'General', } const CATEGORY_STYLES: Record = { BUG: 'bg-red-500/10 text-red-400 border border-red-500/20', FEATURE_REQUEST: 'bg-amber-500/10 text-amber-400 border border-amber-500/20', GENERAL: 'bg-zinc-800 text-zinc-400 border border-zinc-700', } function formatDate(iso: string) { return new Date(iso).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }) } export default function AdminFeedbackPage() { const { getAuthHeaders } = useAuth() const [items, setItems] = useState([]) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [categoryFilter, setCategoryFilter] = useState('ALL') const [reviewedFilter, setReviewedFilter] = useState('ALL') async function load() { try { setLoading(true) setError(null) const res = await fetch('/api/admin/feedback', { headers: getAuthHeaders(), credentials: 'include', }) if (!res.ok) throw new Error(`Failed to load feedback (${res.status})`) setItems(await res.json()) } catch (e: unknown) { setError(e instanceof Error ? e.message : 'Failed to load') } finally { setLoading(false) } } useEffect(() => { load() }, []) // eslint-disable-line react-hooks/exhaustive-deps async function toggleReviewed(item: FeedbackSubmission) { const prev = items setItems((cur) => cur.map((i) => (i.id === item.id ? { ...i, reviewed: !i.reviewed } : i)) ) try { const res = await fetch(`/api/admin/feedback/${item.id}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', ...getAuthHeaders() }, credentials: 'include', body: JSON.stringify({ reviewed: !item.reviewed }), }) if (!res.ok) setItems(prev) } catch { setItems(prev) } } const filtered = items.filter((i) => { if (categoryFilter !== 'ALL' && i.category !== categoryFilter) return false if (reviewedFilter === 'REVIEWED' && !i.reviewed) return false if (reviewedFilter === 'UNREVIEWED' && i.reviewed) return false return true }) return (

Feedback

User-submitted feedback. Mark items reviewed to track triage.

{filtered.length} of {items.length} submissions
{loading &&

Loading…

} {error &&

{error}

} {!loading && !error && filtered.length === 0 && (

No submissions match your filters.

)} {!loading && !error && filtered.length > 0 && (
{filtered.map((item) => ( ))}
Date Category Message Email Reviewed
{formatDate(item.createdAt)} {CATEGORY_LABELS[item.category]} {item.message.length > 120 ? `${item.message.slice(0, 120)}…` : item.message} {item.email ?? '—'}
)}
) }