From 8f2a46d3bb3faeb3af8e756efee643f9630b2755 Mon Sep 17 00:00:00 2001 From: Sean Date: Mon, 30 Mar 2026 19:24:11 -0400 Subject: [PATCH] feat: add FeedbackForm, FeedbackModal, and Give Feedback tab --- components/ChangelogTabs.tsx | 13 +++- components/FeedbackForm.tsx | 129 +++++++++++++++++++++++++++++++++++ components/FeedbackModal.tsx | 51 ++++++++++++++ 3 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 components/FeedbackForm.tsx create mode 100644 components/FeedbackModal.tsx diff --git a/components/ChangelogTabs.tsx b/components/ChangelogTabs.tsx index 87ee996..55119b3 100644 --- a/components/ChangelogTabs.tsx +++ b/components/ChangelogTabs.tsx @@ -3,6 +3,7 @@ import { useEffect, useRef, useState } from 'react' import { PortableTextRenderer } from '@/components/PortableTextRenderer' import { RoadmapItem } from '@/components/RoadmapItem' +import { FeedbackForm } from '@/components/FeedbackForm' import type { ChangelogEntry, RoadmapItemData, Tab, VoteState } from '@/types/changelog' import { useAuth } from '@/context/AuthContext' import Link from 'next/link' @@ -297,9 +298,17 @@ export function ChangelogTabs({ )} - {/* ── Give Feedback — stub, filled in Task 6 ── */} + {/* ── Give Feedback ── */} {activeTab === 'give-feedback' && ( -

Feedback form coming soon.

+
+
+

Tell us what you think.

+

+ Bug, feature idea, or general thought — we read everything. +

+
+ +
)} ) diff --git a/components/FeedbackForm.tsx b/components/FeedbackForm.tsx new file mode 100644 index 0000000..0e21862 --- /dev/null +++ b/components/FeedbackForm.tsx @@ -0,0 +1,129 @@ +'use client' + +import { useState } from 'react' + +type Status = 'idle' | 'loading' | 'done' | 'error' + +const CATEGORIES = [ + { value: 'BUG', label: 'Bug report' }, + { value: 'FEATURE_REQUEST', label: 'Feature request' }, + { value: 'GENERAL', label: 'General feedback' }, +] + +export function FeedbackForm({ onSuccess }: { onSuccess?: () => void }) { + const [category, setCategory] = useState('BUG') + const [message, setMessage] = useState('') + const [email, setEmail] = useState('') + const [status, setStatus] = useState('idle') + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault() + if (!message.trim()) return + setStatus('loading') + try { + const res = await fetch('/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + category, + message: message.trim(), + email: email.trim() || undefined, + }), + }) + if (res.ok) { + setStatus('done') + onSuccess?.() + } else { + setStatus('error') + } + } catch { + setStatus('error') + } + } + + if (status === 'done') { + return ( +
+

Thanks — we got it.

+

+ We read everything. If you left an email we'll follow up. +

+
+ ) + } + + return ( +
+
+
+ + +
+
+ + setEmail(e.target.value)} + placeholder="you@email.com" + disabled={status === 'loading'} + className="w-full rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2.5 text-sm text-zinc-200 placeholder-zinc-600 focus:border-amber-500/40 focus:outline-none disabled:opacity-50" + /> +
+
+ +
+ +