'use client'; import { useWallet } from '@/hooks/useWallet'; import { api } from '@/lib/api'; import { TaskType } from '@/types'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { motion } from 'framer-motion'; import { AlertTriangle, ArrowLeft, CheckCircle2, FileText, Upload, X } from 'lucide-react'; import { useParams, useRouter } from 'next/navigation'; import { useState } from 'react'; import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; export default function SubmitTaskPage() { const params = useParams(); const router = useRouter(); const queryClient = useQueryClient(); const { isConnected } = useWallet(); const taskId = params.taskId as string; const [formData, setFormData] = useState({ text: '', imageFile: null as File | null, labels: '', answers: [] as string[], comment: '', decision: '', customFields: {} as Record }); const [errors, setErrors] = useState>({}); const [imagePreview, setImagePreview] = useState(null); const { data: taskData, isLoading } = useQuery({ queryKey: ['task', taskId], queryFn: () => api.tasks.getById(taskId), }); const submitMutation = useMutation({ mutationFn: (data: any) => api.submissions.submit(data), onSuccess: (response) => { queryClient.invalidateQueries({ queryKey: ['tasks'] }); queryClient.invalidateQueries({ queryKey: ['submissions'] }); const submissionId = response.data.submissionId; router.push(`/submissions/${submissionId}`); }, onError: (error: any) => { alert(error.response?.data?.error?.message || 'Submission failed'); }, }); const task = taskData?.data; const requiredFields: string[] = task?.verificationCriteria?.requiredFields || []; // Helper function to check if a field is required const isFieldRequired = (fieldName: string) => requiredFields.includes(fieldName); // Helper function to render survey questions const getSurveyQuestions = () => { // This could come from the task description or verification criteria // For now, we'll use some default questions based on task type if (task?.taskType === TaskType.SURVEY) { return [ 'How would you rate the overall user experience? (1-5)', 'What features did you find most useful?', 'What improvements would you suggest?' ]; } // You could also get questions from task.verificationCriteria.questions if available return task?.verificationCriteria?.questions || []; }; const handleImageChange = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; // Validate file size (5MB) if (file.size > 5 * 1024 * 1024) { setErrors({ ...errors, image: 'File size must be less than 5MB' }); return; } // Validate file type const validTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp']; if (!validTypes.includes(file.type)) { setErrors({ ...errors, image: 'Only JPG, PNG, WebP images are allowed' }); return; } // Create preview const reader = new FileReader(); reader.onloadend = () => { setImagePreview(reader.result as string); }; reader.readAsDataURL(file); setFormData({ ...formData, imageFile: file }); setErrors({ ...errors, image: '' }); }; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!isConnected) { alert('Please connect your wallet first'); return; } // Dynamic validation based on verification criteria const newErrors: Record = {}; requiredFields.forEach((field: string) => { switch (field) { case 'text': if (!formData.text.trim()) { newErrors.text = 'Text is required'; } break; case 'image': if (!formData.imageFile) { newErrors.image = 'Image is required'; } break; case 'labels': if (!formData.labels.trim()) { newErrors.labels = 'Labels are required'; } break; case 'answers': if (!formData.answers || formData.answers.length === 0 || formData.answers.some(answer => !answer?.trim())) { newErrors.answers = 'All survey questions must be answered'; } break; case 'comment': if (!formData.comment.trim()) { newErrors.comment = 'Comment is required'; } break; case 'decision': if (!formData.decision) { newErrors.decision = 'Decision is required'; } break; } }); if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } // Prepare submission data dynamically const submissionData: any = {}; requiredFields.forEach((field: string) => { switch (field) { case 'text': submissionData.text = formData.text; break; case 'image': // In production, upload to cloud storage (S3, Cloudinary, etc.) submissionData.imageUrls = ['https://placeholder.com/image.jpg']; submissionData.metadata = { fileName: formData.imageFile?.name }; break; case 'labels': submissionData.labels = formData.labels.split(',').map(label => label.trim()); break; case 'answers': submissionData.answers = formData.answers; break; case 'comment': submissionData.comment = formData.comment; break; case 'decision': submissionData.decision = formData.decision; break; } }); // Submit await submitMutation.mutateAsync({ taskId, submissionData, }); }; if (isLoading) { return (
); } if (!task) { return (

Task not found

); } return (
{/* Animated Background */}
{/* Back Button */} {/* Task Info Card */}

{task.title}

{task.description}

Payment: ${task.paymentAmount} cUSD
{/* Submission Form */}

Submit Your Work

{/* Text Field - Dynamic based on required fields */} {isFieldRequired('text') && (