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;
|
position: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface UserReview {
|
||||||
|
id: string;
|
||||||
|
rating: number;
|
||||||
|
title: string;
|
||||||
|
body: string;
|
||||||
|
is_approved: boolean;
|
||||||
|
created_at: string;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ProductDetail() {
|
export default function ProductDetail() {
|
||||||
const { slug } = useParams<{ slug: string }>();
|
const { slug } = useParams<{ slug: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@@ -54,7 +63,7 @@ export default function ProductDetail() {
|
|||||||
const [hasAccess, setHasAccess] = useState(false);
|
const [hasAccess, setHasAccess] = useState(false);
|
||||||
const [checkingAccess, setCheckingAccess] = useState(true);
|
const [checkingAccess, setCheckingAccess] = useState(true);
|
||||||
const [expandedModules, setExpandedModules] = useState<Set<string>>(new Set());
|
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 [reviewModalOpen, setReviewModalOpen] = useState(false);
|
||||||
const { addItem, items } = useCart();
|
const { addItem, items } = useCart();
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
@@ -165,13 +174,17 @@ export default function ProductDetail() {
|
|||||||
|
|
||||||
const { data } = await supabase
|
const { data } = await supabase
|
||||||
.from('reviews')
|
.from('reviews')
|
||||||
.select('id')
|
.select('id, rating, title, body, is_approved, created_at')
|
||||||
.eq('user_id', user.id)
|
.eq('user_id', user.id)
|
||||||
.eq('product_id', product.id)
|
.eq('product_id', product.id)
|
||||||
.eq('is_approved', true)
|
.order('created_at', { ascending: false })
|
||||||
.limit(1);
|
.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)
|
// Check if webinar has ended (eligible for review)
|
||||||
@@ -403,14 +416,59 @@ export default function ProductDetail() {
|
|||||||
|
|
||||||
{/* Webinar review prompt */}
|
{/* Webinar review prompt */}
|
||||||
{hasAccess && product.type === 'webinar' && isWebinarEnded() && (
|
{hasAccess && product.type === 'webinar' && isWebinarEnded() && (
|
||||||
<Card className="border-2 border-primary/20 mt-6">
|
<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-4">
|
<CardContent className="py-6">
|
||||||
{hasReviewed ? (
|
{userReview ? (
|
||||||
<div className="flex items-center gap-2 text-muted-foreground">
|
userReview.is_approved ? (
|
||||||
<CheckCircle className="w-5 h-5 text-accent" />
|
// Approved review - celebratory display
|
||||||
<span>Terima kasih atas ulasan Anda (menunggu moderasi)</span>
|
<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>
|
</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 className="flex items-center justify-between gap-4 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Bagaimana pengalaman webinar ini?</p>
|
<p className="font-medium">Bagaimana pengalaman webinar ini?</p>
|
||||||
@@ -442,7 +500,7 @@ export default function ProductDetail() {
|
|||||||
productId={product.id}
|
productId={product.id}
|
||||||
type="webinar"
|
type="webinar"
|
||||||
contextLabel={product.title}
|
contextLabel={product.title}
|
||||||
onSuccess={() => setHasReviewed(true)}
|
onSuccess={() => checkUserReview()}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AppLayout>
|
</AppLayout>
|
||||||
|
|||||||
Reference in New Issue
Block a user