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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user