import { useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import { authFetch } from '../lib/auth'; import { API_ROUTES } from '../lib/api-routes'; import { apiUrl, jsonHeaders } from '../lib/api-client'; interface Props { locale: string; feedback: { title: string; typeLabel: string; typeBug: string; typeSuggestion: string; typeOther: string; contentLabel: string; contentPlaceholder: string; imagesLabel: string; anonymousLabel: string; anonymousHint: string; submitBtn: string; submitting: string; success: string; error: string; contentRequired: string; contentTooLong: string; tooManyImages: string; imageTooLarge: string }; } type FeedbackType = 'bug' | 'suggestion' | 'other'; const MAX_IMAGES = 3; const MAX_CONTENT_SIZE = 500; const MAX_IMAGE_SIZE = 5 * 1024 * 1024; export default function FeedbackPage({ locale, feedback: f }: Props) { const navigate = useNavigate(); const [selectedType, setSelectedType] = useState('bug'); const [content, setContent] = useState(''); const [images, setImages] = useState([]); const [isAnonymous, setIsAnonymous] = useState(false); const [submitting, setSubmitting] = useState(false); const [toast, setToast] = useState<{ type: 'success' | 'error'; message: string } | null>(null); const fileInputRef = useRef(null); const typeOptions: { value: FeedbackType; label: string }[] = [ { value: 'bug', label: f.typeBug }, { value: 'suggestion', label: f.typeSuggestion }, { value: 'other', label: f.typeOther }, ]; const handleImagePick = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; if (images.length >= MAX_IMAGES) { setToast({ type: 'error', message: f.tooManyImages }); return; } if (file.size > MAX_IMAGE_SIZE) { setToast({ type: 'error', message: f.imageTooLarge }); return; } setImages([...images, file]); if (fileInputRef.current) fileInputRef.current.value = ''; }; const handleRemoveImage = (index: number) => { setImages(images.filter((_, i) => i !== index)); }; const handleSubmit = async () => { const trimmedContent = content.trim(); if (!trimmedContent) { setToast({ type: 'error', message: f.contentRequired }); return; } if (trimmedContent.length > MAX_CONTENT_SIZE) { setToast({ type: 'error', message: f.contentTooLong }); return; } setSubmitting(true); setToast(null); try { const formData = new FormData(); formData.append('feedback_type', selectedType); formData.append('content', trimmedContent); formData.append('device_info', JSON.stringify({ platform: 'web', model: navigator.userAgent })); formData.append('app_version', '1.0.0'); formData.append('os_version', navigator.platform); for (const image of images) { formData.append('images', image); } if (isAnonymous) { // Anonymous submission - no auth header const res = await fetch(apiUrl(API_ROUTES.feedback.submit), { method: 'POST', body: formData, }); if (!res.ok) { throw new Error('Submit failed'); } } else { // Authenticated submission await authFetch(API_ROUTES.feedback.submit, { method: 'POST', body: formData, }); } setToast({ type: 'success', message: f.success }); setTimeout(() => navigate(`/${locale}/settings`), 1500); } catch { setToast({ type: 'error', message: f.error }); } finally { setSubmitting(false); } }; return (
{/* Page Header */}

{f.title}

{/* Toast */} {toast && (
{toast.message}
)} {/* Feedback Type */}

{f.typeLabel}

{typeOptions.map((opt) => ( ))}
{/* Content */}

{f.contentLabel}