Add celebratory review display after approval

Changes:
- Fetch user's full review data (not just approval status)
- Show celebratory UI when review is approved:
  - Gradient background with brand accent colors
  - "Ulasan Anda Terbit!" heading with approval badge
  - Display user's review with star rating
  - Thank you message for contributing
- Show pending state with clock icon while waiting approval
- Update review modal to refresh data after submission

This creates a proud moment for users when their review is approved!

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
dwindown
2025-12-26 00:41:12 +07:00
parent b4d3b1a580
commit ed0d1b0ac8

View File

@@ -45,6 +45,15 @@ interface Lesson {
position: number;
}
interface UserReview {
id: string;
rating: number;
title: string;
body: string;
is_approved: boolean;
created_at: string;
}
export default function ProductDetail() {
const { slug } = useParams<{ slug: string }>();
const navigate = useNavigate();
@@ -54,7 +63,7 @@ export default function ProductDetail() {
const [hasAccess, setHasAccess] = useState(false);
const [checkingAccess, setCheckingAccess] = useState(true);
const [expandedModules, setExpandedModules] = useState<Set<string>>(new Set());
const [hasReviewed, setHasReviewed] = useState(false);
const [userReview, setUserReview] = useState<UserReview | null>(null);
const [reviewModalOpen, setReviewModalOpen] = useState(false);
const { addItem, items } = useCart();
const { user } = useAuth();
@@ -165,13 +174,17 @@ export default function ProductDetail() {
const { data } = await supabase
.from('reviews')
.select('id')
.select('id, rating, title, body, is_approved, created_at')
.eq('user_id', user.id)
.eq('product_id', product.id)
.eq('is_approved', true)
.order('created_at', { ascending: false })
.limit(1);
setHasReviewed(!!(data && data.length > 0));
if (data && data.length > 0) {
setUserReview(data[0] as UserReview);
} else {
setUserReview(null);
}
};
// Check if webinar has ended (eligible for review)
@@ -403,14 +416,59 @@ export default function ProductDetail() {
{/* Webinar review prompt */}
{hasAccess && product.type === 'webinar' && isWebinarEnded() && (
<Card className="border-2 border-primary/20 mt-6">
<CardContent className="py-4">
{hasReviewed ? (
<div className="flex items-center gap-2 text-muted-foreground">
<CheckCircle className="w-5 h-5 text-accent" />
<span>Terima kasih atas ulasan Anda (menunggu moderasi)</span>
</div>
<Card className={`border-2 mt-6 ${userReview?.is_approved ? 'bg-gradient-to-br from-brand-accent/10 to-primary/10 border-brand-accent/30' : 'border-primary/20'}`}>
<CardContent className="py-6">
{userReview ? (
userReview.is_approved ? (
// Approved review - celebratory display
<div className="space-y-4">
<div className="flex items-start gap-3">
<div className="rounded-full bg-brand-accent p-2">
<CheckCircle className="w-6 h-6 text-white" />
</div>
<div className="flex-1">
<div className="flex items-center gap-2 mb-1">
<h3 className="text-lg font-bold">Ulasan Anda Terbit!</h3>
<Badge className="bg-brand-accent text-white rounded-full">Disetujui</Badge>
</div>
<p className="text-sm text-muted-foreground">Terima kasih telah berbagi pengalaman Anda. Ulasan Anda membantu peserta lain!</p>
</div>
</div>
{/* User's review display */}
<div className="bg-background/50 backdrop-blur rounded-lg p-4 border border-brand-accent/20">
<div className="flex gap-0.5 mb-2">
{[1, 2, 3, 4, 5].map((i) => (
<Star
key={i}
className={`w-5 h-5 ${i <= userReview.rating ? 'fill-brand-accent text-brand-accent' : 'text-muted-foreground'}`}
/>
))}
</div>
<h4 className="font-semibold text-base mb-1">{userReview.title}</h4>
{userReview.body && (
<p className="text-sm text-muted-foreground">{userReview.body}</p>
)}
</div>
<div className="text-xs text-muted-foreground">
Diterbitkan pada {new Date(userReview.created_at).toLocaleDateString('id-ID', { day: 'numeric', month: 'long', year: 'numeric' })}
</div>
</div>
) : (
// Pending review
<div className="flex items-center gap-3 text-muted-foreground">
<div className="rounded-full bg-amber-500/10 p-2">
<Clock className="w-5 h-5 text-amber-500" />
</div>
<div>
<p className="font-medium text-foreground">Ulasan Anda sedang ditinjau</p>
<p className="text-sm">Terima kasih! Ulasan akan muncul setelah disetujui admin.</p>
</div>
</div>
)
) : (
// No review yet - prompt to review
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<p className="font-medium">Bagaimana pengalaman webinar ini?</p>
@@ -442,7 +500,7 @@ export default function ProductDetail() {
productId={product.id}
type="webinar"
contextLabel={product.title}
onSuccess={() => setHasReviewed(true)}
onSuccess={() => checkUserReview()}
/>
)}
</AppLayout>