Files
shadow-gunbuilder-ai-proto/components/FeedbackForm.tsx
T

134 lines
4.6 KiB
TypeScript

'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<Status>('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 (
<div className="py-6 text-center">
<p className="text-sm font-semibold text-amber-400">Thanks we got it.</p>
<p className="mt-1 text-xs text-zinc-500">
We read everything. If you left an email we&apos;ll follow up.
</p>
</div>
)
}
return (
<form onSubmit={handleSubmit} className="space-y-5">
<div className="grid grid-cols-1 gap-5 sm:grid-cols-2">
<div>
<label htmlFor="feedback-category" className="mb-2 block text-[11px] font-semibold uppercase tracking-wider text-zinc-500">
Category
</label>
<select
id="feedback-category"
value={category}
onChange={(e) => setCategory(e.target.value)}
disabled={status === 'loading'}
autoFocus
className="w-full appearance-none rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2.5 text-sm text-zinc-200 focus:border-amber-500/40 focus:outline-none disabled:opacity-50"
>
{CATEGORIES.map((c) => (
<option key={c.value} value={c.value}>
{c.label}
</option>
))}
</select>
</div>
<div>
<label htmlFor="feedback-email" className="mb-2 block text-[11px] font-semibold uppercase tracking-wider text-zinc-500">
Email{' '}
<span className="font-normal normal-case tracking-normal text-zinc-600">
(optional if you want a reply)
</span>
</label>
<input
id="feedback-email"
type="email"
value={email}
onChange={(e) => 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"
/>
</div>
</div>
<div>
<label htmlFor="feedback-message" className="mb-2 block text-[11px] font-semibold uppercase tracking-wider text-zinc-500">
Message
</label>
<textarea
id="feedback-message"
value={message}
onChange={(e) => setMessage(e.target.value.slice(0, 1000))}
placeholder="What's on your mind?"
rows={5}
required
disabled={status === 'loading'}
className="w-full resize-y rounded-lg border border-zinc-800 bg-zinc-950 px-3 py-2.5 text-sm leading-relaxed text-zinc-200 placeholder-zinc-600 focus:border-amber-500/40 focus:outline-none disabled:opacity-50"
/>
<p className="mt-1.5 text-right text-[11px] text-zinc-600">
{message.length} / 1000
</p>
</div>
<div className="flex items-center justify-between gap-4">
<p className="text-[11px] text-zinc-600">
We&apos;ll only use your email to follow up on your feedback.
</p>
<button
type="submit"
disabled={status === 'loading' || !message.trim()}
className="shrink-0 rounded-lg bg-amber-500/90 px-6 py-2.5 text-sm font-bold text-black hover:bg-amber-400 disabled:opacity-50"
>
{status === 'loading' ? 'Sending…' : 'Send Feedback →'}
</button>
</div>
{status === 'error' && (
<p className="text-xs text-red-400">Something went wrong. Try again.</p>
)}
</form>
)
}