322 lines
12 KiB
TypeScript
322 lines
12 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from 'react'
|
|
import { Header } from '@/components/header'
|
|
import { Footer } from '@/components/footer'
|
|
import { useAuth } from '@/contexts/AuthContext'
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
|
|
import { Input } from '@/components/ui/input'
|
|
import { Label } from '@/components/ui/label'
|
|
import { Textarea } from '@/components/ui/textarea'
|
|
import { Button } from '@/components/ui/button'
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
|
import { Alert, AlertDescription } from '@/components/ui/alert'
|
|
import { Switch } from '@/components/ui/switch'
|
|
import { MessageSquare, AlertTriangle, Send, CheckCircle, AlertCircle } from 'lucide-react'
|
|
|
|
export function FeedbackPageClient() {
|
|
const { user } = useAuth()
|
|
|
|
const [formType, setFormType] = useState<'suggestion' | 'report'>('suggestion')
|
|
const [formData, setFormData] = useState({
|
|
name: user?.name || '',
|
|
email: user?.email || '',
|
|
title: '',
|
|
details: '',
|
|
category: 'general',
|
|
urgency: 'low'
|
|
})
|
|
const [errors, setErrors] = useState<Record<string, string>>({})
|
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
const [isSuccess, setIsSuccess] = useState(false)
|
|
|
|
const categoryOptions = [
|
|
{ value: 'general', label: 'General' },
|
|
{ value: 'ui', label: 'User Interface' },
|
|
{ value: 'performance', label: 'Performance' },
|
|
{ value: 'feature', label: 'Feature Request' },
|
|
{ value: 'technical', label: 'Technical Issue' },
|
|
{ value: 'billing', label: 'Billing/Payment' },
|
|
{ value: 'security', label: 'Security' },
|
|
{ value: 'other', label: 'Other' }
|
|
]
|
|
|
|
const urgencyOptions = [
|
|
{ value: 'low', label: 'Low - Not urgent' },
|
|
{ value: 'medium', label: 'Medium - Needs attention' },
|
|
{ value: 'high', label: 'High - Impacts usage' },
|
|
{ value: 'critical', label: 'Critical - Service unavailable' }
|
|
]
|
|
|
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
e.preventDefault()
|
|
setIsSubmitting(true)
|
|
setErrors({})
|
|
|
|
// Client-side validation
|
|
const newErrors: Record<string, string> = {}
|
|
if (!formData.name.trim()) newErrors.name = 'Please enter your name.'
|
|
if (!formData.email.trim() || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
|
newErrors.email = 'Please enter a valid email address.'
|
|
}
|
|
if (!formData.title.trim()) newErrors.title = `Please enter a title for your ${formType}.`
|
|
if (!formData.details.trim()) newErrors.details = `Please provide details for your ${formType}.`
|
|
|
|
if (Object.keys(newErrors).length > 0) {
|
|
setErrors(newErrors)
|
|
setIsSubmitting(false)
|
|
return
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/feedback', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
...formData,
|
|
type: formType,
|
|
siliconId: user?.id || null
|
|
}),
|
|
})
|
|
|
|
const result = await response.json()
|
|
|
|
if (response.ok && result.success) {
|
|
setIsSuccess(true)
|
|
setFormData({
|
|
name: user?.name || '',
|
|
email: user?.email || '',
|
|
title: '',
|
|
details: '',
|
|
category: 'general',
|
|
urgency: 'low'
|
|
})
|
|
setFormType('suggestion')
|
|
} else {
|
|
if (result.errors) {
|
|
setErrors(result.errors)
|
|
} else {
|
|
setErrors({ submit: result.message || 'Error submitting form. Please try again.' })
|
|
}
|
|
}
|
|
} catch (error) {
|
|
setErrors({ submit: 'Network error. Please try again.' })
|
|
} finally {
|
|
setIsSubmitting(false)
|
|
}
|
|
}
|
|
|
|
const handleInputChange = (field: string, value: string) => {
|
|
setFormData(prev => ({ ...prev, [field]: value }))
|
|
if (errors[field]) {
|
|
setErrors(prev => ({ ...prev, [field]: '' }))
|
|
}
|
|
}
|
|
|
|
const handleTypeToggle = (checked: boolean) => {
|
|
setFormType(checked ? 'report' : 'suggestion')
|
|
setIsSuccess(false)
|
|
}
|
|
|
|
return (
|
|
<div className="min-h-screen bg-background">
|
|
<Header />
|
|
<main className="container max-w-4xl pt-24 pb-8">
|
|
{/* Page Header */}
|
|
<div className="text-center mb-12">
|
|
<h1 className="text-4xl font-bold tracking-tight mb-4">Suggestion or Report</h1>
|
|
<p className="text-xl text-muted-foreground max-w-3xl mx-auto">
|
|
We value your feedback! Use this form to submit suggestions or report any issues you've encountered.
|
|
</p>
|
|
</div>
|
|
|
|
<Card>
|
|
<CardContent className="pt-6">
|
|
{/* Success Message */}
|
|
{isSuccess && (
|
|
<Alert className="mb-6 border-green-200 bg-green-50 dark:bg-green-950/10">
|
|
<CheckCircle className="h-4 w-4 text-green-600" />
|
|
<AlertDescription className="text-green-800 dark:text-green-200">
|
|
{formType === 'suggestion'
|
|
? 'Thank you for your suggestion! We appreciate your feedback.'
|
|
: 'Your report has been submitted. We will look into it as soon as possible.'
|
|
}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
{/* Error Message */}
|
|
{errors.submit && (
|
|
<Alert className="mb-6 border-red-200 bg-red-50 dark:bg-red-950/10">
|
|
<AlertCircle className="h-4 w-4 text-red-600" />
|
|
<AlertDescription className="text-red-800 dark:text-red-200">
|
|
{errors.submit}
|
|
</AlertDescription>
|
|
</Alert>
|
|
)}
|
|
|
|
<form onSubmit={handleSubmit} className="space-y-6">
|
|
{/* Form Type Toggle */}
|
|
<div className="flex items-center justify-center space-x-4 p-4 bg-muted rounded-lg">
|
|
<div className={`flex items-center space-x-2 ${formType === 'suggestion' ? 'text-primary font-medium' : 'text-muted-foreground'}`}>
|
|
<MessageSquare className="w-4 h-4" />
|
|
<span>Suggestion</span>
|
|
</div>
|
|
|
|
<Switch
|
|
checked={formType === 'report'}
|
|
onCheckedChange={handleTypeToggle}
|
|
className="data-[state=checked]:bg-orange-600"
|
|
/>
|
|
|
|
<div className={`flex items-center space-x-2 ${formType === 'report' ? 'text-orange-600 font-medium' : 'text-muted-foreground'}`}>
|
|
<AlertTriangle className="w-4 h-4" />
|
|
<span>Report</span>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Name and Email */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="name">Name <span className="text-red-600">*</span></Label>
|
|
<Input
|
|
id="name"
|
|
type="text"
|
|
placeholder="Your full name"
|
|
value={formData.name}
|
|
onChange={(e) => handleInputChange('name', e.target.value)}
|
|
className={errors.name ? 'border-red-500' : ''}
|
|
/>
|
|
{errors.name && (
|
|
<p className="text-sm text-red-600">{errors.name}</p>
|
|
)}
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label htmlFor="email">Email <span className="text-red-600">*</span></Label>
|
|
<Input
|
|
id="email"
|
|
type="email"
|
|
placeholder="your.email@example.com"
|
|
value={formData.email}
|
|
onChange={(e) => handleInputChange('email', e.target.value)}
|
|
className={errors.email ? 'border-red-500' : ''}
|
|
/>
|
|
{errors.email && (
|
|
<p className="text-sm text-red-600">{errors.email}</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Title */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="title">
|
|
{formType === 'suggestion' ? 'Suggestion Title' : 'Report Title'} <span className="text-red-600">*</span>
|
|
</Label>
|
|
<Input
|
|
id="title"
|
|
type="text"
|
|
placeholder={formType === 'suggestion'
|
|
? 'Brief title for your suggestion'
|
|
: 'Brief title for your report'
|
|
}
|
|
value={formData.title}
|
|
onChange={(e) => handleInputChange('title', e.target.value)}
|
|
className={errors.title ? 'border-red-500' : ''}
|
|
/>
|
|
{errors.title && (
|
|
<p className="text-sm text-red-600">{errors.title}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Category and Urgency */}
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label htmlFor="category">Category</Label>
|
|
<Select
|
|
value={formData.category}
|
|
onValueChange={(value) => handleInputChange('category', value)}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{categoryOptions.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
|
|
{/* Urgency (only for reports) */}
|
|
{formType === 'report' && (
|
|
<div className="space-y-2">
|
|
<Label htmlFor="urgency">Urgency Level</Label>
|
|
<Select
|
|
value={formData.urgency}
|
|
onValueChange={(value) => handleInputChange('urgency', value)}
|
|
>
|
|
<SelectTrigger className="border-orange-200 focus:border-orange-400">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{urgencyOptions.map((option) => (
|
|
<SelectItem key={option.value} value={option.value}>
|
|
{option.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Details */}
|
|
<div className="space-y-2">
|
|
<Label htmlFor="details">Details <span className="text-red-600">*</span></Label>
|
|
<Textarea
|
|
id="details"
|
|
placeholder={formType === 'suggestion'
|
|
? 'Please describe your suggestion in detail. What would you like to see improved or added?'
|
|
: 'Please describe the issue in detail. What happened, and what were you trying to do?'
|
|
}
|
|
rows={6}
|
|
value={formData.details}
|
|
onChange={(e) => handleInputChange('details', e.target.value)}
|
|
className={errors.details ? 'border-red-500' : ''}
|
|
/>
|
|
{errors.details && (
|
|
<p className="text-sm text-red-600">{errors.details}</p>
|
|
)}
|
|
</div>
|
|
|
|
{/* Submit Button */}
|
|
<Button
|
|
type="submit"
|
|
className={`w-full md:w-auto ${formType === 'report' ? 'bg-orange-600 hover:bg-orange-700' : ''}`}
|
|
disabled={isSubmitting}
|
|
>
|
|
{isSubmitting ? (
|
|
<>
|
|
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin mr-2" />
|
|
Submitting...
|
|
</>
|
|
) : (
|
|
<>
|
|
<Send className="w-4 h-4 mr-2" />
|
|
Submit {formType === 'suggestion' ? 'Suggestion' : 'Report'}
|
|
</>
|
|
)}
|
|
</Button>
|
|
</form>
|
|
</CardContent>
|
|
</Card>
|
|
</main>
|
|
<Footer />
|
|
</div>
|
|
)
|
|
} |