From dbe547bb0537f1cea1b267b862c1add23dcfad16 Mon Sep 17 00:00:00 2001 From: "gpt-engineer-app[bot]" <159125892+gpt-engineer-app[bot]@users.noreply.github.com> Date: Sat, 20 Sep 2025 01:18:31 +0000 Subject: [PATCH] Implement reviews system --- src/components/reviews/ReviewForm.tsx | 212 ++++++++++++++++++++++ src/components/reviews/ReviewsList.tsx | 197 ++++++++++++++++++++ src/components/reviews/ReviewsSection.tsx | 111 +++++++++++ src/pages/ParkDetail.tsx | 27 ++- src/pages/RideDetail.tsx | 15 +- 5 files changed, 546 insertions(+), 16 deletions(-) create mode 100644 src/components/reviews/ReviewForm.tsx create mode 100644 src/components/reviews/ReviewsList.tsx create mode 100644 src/components/reviews/ReviewsSection.tsx diff --git a/src/components/reviews/ReviewForm.tsx b/src/components/reviews/ReviewForm.tsx new file mode 100644 index 00000000..e004d44a --- /dev/null +++ b/src/components/reviews/ReviewForm.tsx @@ -0,0 +1,212 @@ +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Star, Send } from 'lucide-react'; +import { useAuth } from '@/hooks/useAuth'; +import { supabase } from '@/integrations/supabase/client'; +import { toast } from '@/hooks/use-toast'; + +const reviewSchema = z.object({ + rating: z.number().min(1).max(5), + title: z.string().optional(), + content: z.string().min(10, 'Review must be at least 10 characters long'), + visit_date: z.string().optional(), + wait_time_minutes: z.number().optional() +}); + +type ReviewFormData = z.infer; + +interface ReviewFormProps { + entityType: 'park' | 'ride'; + entityId: string; + entityName: string; + onReviewSubmitted: () => void; +} + +export function ReviewForm({ entityType, entityId, entityName, onReviewSubmitted }: ReviewFormProps) { + const { user } = useAuth(); + const [rating, setRating] = useState(0); + const [submitting, setSubmitting] = useState(false); + + const { + register, + handleSubmit, + reset, + setValue, + formState: { errors } + } = useForm({ + resolver: zodResolver(reviewSchema) + }); + + const handleRatingClick = (selectedRating: number) => { + setRating(selectedRating); + setValue('rating', selectedRating); + }; + + const onSubmit = async (data: ReviewFormData) => { + if (!user) { + toast({ + title: "Authentication Required", + description: "Please sign in to submit a review.", + variant: "destructive" + }); + return; + } + + setSubmitting(true); + try { + const reviewData = { + user_id: user.id, + rating: data.rating, + title: data.title || null, + content: data.content, + visit_date: data.visit_date || null, + wait_time_minutes: data.wait_time_minutes || null, + moderation_status: 'pending' as const, + ...(entityType === 'park' ? { park_id: entityId } : { ride_id: entityId }) + }; + + const { error } = await supabase + .from('reviews') + .insert([reviewData]); + + if (error) throw error; + + toast({ + title: "Review Submitted!", + description: "Thank you for your review. It will be published after moderation." + }); + + reset(); + setRating(0); + onReviewSubmitted(); + } catch (error) { + console.error('Error submitting review:', error); + toast({ + title: "Error", + description: "Failed to submit review. Please try again.", + variant: "destructive" + }); + } finally { + setSubmitting(false); + } + }; + + if (!user) { + return ( + + + +

Share Your Experience

+

+ Sign in to write a review for {entityName} +

+ +
+
+ ); + } + + return ( + + + Write a Review for {entityName} + + +
+ {/* Rating */} +
+ +
+ {Array.from({ length: 5 }, (_, i) => ( + + ))} + {rating > 0 && ( + + {rating} star{rating !== 1 ? 's' : ''} + + )} +
+ {errors.rating && ( +

Please select a rating

+ )} +
+ + {/* Title */} +
+ + +
+ + {/* Content */} +
+ +