From 7f495749abec98cdece32c8ae298b3dbd2231883 Mon Sep 17 00:00:00 2001 From: jvcByte Date: Fri, 12 Jun 2026 01:37:42 +0100 Subject: [PATCH] Move feedback to public route (app/feedback) to allow anonymous access --- app/feedback/FeedbackForm.tsx | 231 ++++++++++++++++++++++++++++++++++ app/feedback/page.tsx | 27 ++++ 2 files changed, 258 insertions(+) create mode 100644 app/feedback/FeedbackForm.tsx create mode 100644 app/feedback/page.tsx diff --git a/app/feedback/FeedbackForm.tsx b/app/feedback/FeedbackForm.tsx new file mode 100644 index 0000000..ca1f8b8 --- /dev/null +++ b/app/feedback/FeedbackForm.tsx @@ -0,0 +1,231 @@ +'use client'; + +import { useState } from 'react'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { Frown, Meh, Smile, Laugh, Heart } from 'lucide-react'; + +export default function FeedbackForm({ isAuthenticated }: { isAuthenticated: boolean }) { + const router = useRouter(); + const [rating, setRating] = useState(null); + const [comments, setComments] = useState(''); + const [challenges, setChallenges] = useState(''); + const [improvements, setImprovements] = useState(''); + const [malfunctions, setMalfunctions] = useState(''); + const [attachmentUrl, setAttachmentUrl] = useState(''); + const [anonymousName, setAnonymousName] = useState(''); + const [anonymousEmail, setAnonymousEmail] = useState(''); + const [anonymousMatric, setAnonymousMatric] = useState(''); + const [submitting, setSubmitting] = useState(false); + + const ratingConfig = [ + { value: 1, icon: Frown, label: 'Poor', color: '#ef4444' }, + { value: 2, icon: Meh, label: 'Fair', color: '#f97316' }, + { value: 3, icon: Smile, label: 'Good', color: '#eab308' }, + { value: 4, icon: Laugh, label: 'Great', color: '#84cc16' }, + { value: 5, icon: Heart, label: 'Excellent', color: '#10b981' }, + ]; + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + + // Validate name for anonymous users + if (!isAuthenticated && !anonymousName.trim()) { + toast.error('Please provide your name'); + return; + } + + setSubmitting(true); + + try { + const res = await fetch('/api/feedback', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + rating, + comments, + challenges, + improvements, + malfunctions, + attachment_url: attachmentUrl || null, + anonymous_name: !isAuthenticated ? anonymousName : undefined, + anonymous_email: !isAuthenticated ? anonymousEmail : undefined, + anonymous_matric: !isAuthenticated ? anonymousMatric : undefined, + }), + }); + + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error ?? 'Failed to submit feedback'); + } + + toast.success('Thank you for your feedback!'); + router.push('/'); + } catch (err) { + toast.error(err instanceof Error ? err.message : 'Failed to submit'); + } finally { + setSubmitting(false); + } + } + + return ( +
+ {/* Anonymous User Info */} + {!isAuthenticated && ( +
+
+ Your Information + Required +
+
+ setAnonymousName(e.target.value)} + placeholder="Name / Username *" + required + style={{ border: 'none', background: 'var(--bg2)' }} + /> + setAnonymousEmail(e.target.value)} + placeholder="Email (optional)" + style={{ border: 'none', background: 'var(--bg2)' }} + /> + setAnonymousMatric(e.target.value)} + placeholder="Matric Number (optional)" + style={{ border: 'none', background: 'var(--bg2)' }} + /> +
+
+ )} + + {/* Rating */} +
+
+ Overall Experience +
+
+ {ratingConfig.map(({ value, icon: Icon, label, color }) => ( + + ))} +
+
+ + {/* Challenges */} +
+
+ Challenges Encountered +
+