Files
meet-hub/src/components/reviews/ProductReviews.tsx
dwindown e347a780f8 Fix review system: display real names and check approval status
Changes:
- ProductReviews.tsx: Use LEFT JOIN and fetch reviewer_name field
- ReviewModal.tsx: Store reviewer_name at submission time
- ProductDetail.tsx: Check is_approved=true in checkUserReview()
- Add migration for reviewer_name column and approval index

This fixes two issues:
1. Reviews now show real account names instead of "Anonymous"
2. Members no longer see "menunggu moderasi" after approval

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 22:29:48 +07:00

86 lines
2.4 KiB
TypeScript

import { useEffect, useState } from 'react';
import { supabase } from '@/integrations/supabase/client';
import { ReviewCard } from './ReviewCard';
import { Star } from 'lucide-react';
interface Review {
id: string;
rating: number;
title: string;
body: string;
created_at: string;
reviewer_name: string | null;
profiles: { name: string | null } | null;
}
interface ProductReviewsProps {
productId: string;
type?: string;
}
export function ProductReviews({ productId, type }: ProductReviewsProps) {
const [reviews, setReviews] = useState<Review[]>([]);
const [stats, setStats] = useState({ avg: 0, count: 0 });
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchReviews();
}, [productId, type]);
const fetchReviews = async () => {
let query = supabase
.from('reviews')
.select('id, rating, title, body, created_at, reviewer_name, profiles!user_id (name)')
.eq('is_approved', true);
if (productId) {
query = query.eq('product_id', productId);
} else if (type) {
query = query.eq('type', type);
}
const { data } = await query.order('created_at', { ascending: false }).limit(3);
if (data && data.length > 0) {
const typedData = data as unknown as Review[];
setReviews(typedData);
const avg = typedData.reduce((sum, r) => sum + r.rating, 0) / typedData.length;
setStats({ avg: Math.round(avg * 10) / 10, count: typedData.length });
}
setLoading(false);
};
if (loading || reviews.length === 0) return null;
return (
<div className="space-y-4">
<div className="flex items-center gap-3">
<div className="flex gap-0.5">
{[1, 2, 3, 4, 5].map((i) => (
<Star
key={i}
className={`w-5 h-5 ${
i <= Math.round(stats.avg) ? 'fill-primary text-primary' : 'text-muted-foreground'
}`}
/>
))}
</div>
<span className="font-bold">{stats.avg}</span>
<span className="text-muted-foreground">({stats.count} ulasan)</span>
</div>
<div className="grid gap-4">
{reviews.map((review) => (
<ReviewCard
key={review.id}
rating={review.rating}
title={review.title}
body={review.body}
authorName={review.reviewer_name || review.profiles?.name || 'Anonymous'}
date={review.created_at}
/>
))}
</div>
</div>
);
}