Display bootcamp lesson chapters on Product Detail page as marketing content
This commit implements displaying lesson chapters/timeline as marketing content on the Product Detail page for bootcamp products, helping potential buyers understand the detailed breakdown of what they'll learn. ## Changes ### Product Detail Page (src/pages/ProductDetail.tsx) - Updated Lesson interface to include optional chapters property - Modified fetchCurriculum to fetch chapters along with lessons - Enhanced renderCurriculumPreview to display chapters as nested content under lessons - Chapters shown with timestamps and titles, clickable to navigate to bootcamp access page - Visual hierarchy: Module → Lesson → Chapters with proper indentation and styling ### Review System Fixes - Fixed review prompt re-appearing after submission (before admin approval) - Added hasSubmittedReview check to prevent showing prompt when review exists - Fixed edit review functionality to pre-populate form with existing data - ReviewModal now handles both INSERT (new) and UPDATE (edit) operations - Edit resets is_approved to false requiring re-approval ### Video Player Enhancements - Implemented Adilo/Video.js integration for M3U8/HLS playback - Added video progress tracking with refs pattern for reliability - Implemented chapter navigation for both Adilo and YouTube players - Added keyboard shortcuts (Space, Arrows, F, M, J, L) - Resume prompt for returning users with saved progress ### Database Migrations - Added Adilo video support fields (m3u8_url, mp4_url, video_host) - Created video_progress table for tracking user watch progress - Fixed consulting slots user_id foreign key - Added chapters support to products and bootcamp_lessons tables ### Documentation - Added Adilo implementation plan and quick reference docs - Cleaned up transcript analysis files 🤖 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,8 +45,8 @@ interface ConsultingSlot {
|
||||
start_time: string;
|
||||
end_time: string;
|
||||
status: string;
|
||||
product_id: string | null;
|
||||
meet_link: string | null;
|
||||
topic_category?: string | null;
|
||||
}
|
||||
|
||||
export default function MemberDashboard() {
|
||||
@@ -144,7 +144,7 @@ export default function MemberDashboard() {
|
||||
// Fetch confirmed consulting slots for quick access
|
||||
supabase
|
||||
.from("consulting_slots")
|
||||
.select("id, date, start_time, end_time, status, product_id, meet_link")
|
||||
.select("id, date, start_time, end_time, status, meet_link, topic_category")
|
||||
.eq("user_id", user!.id)
|
||||
.eq("status", "confirmed")
|
||||
.order("date", { ascending: false }),
|
||||
@@ -178,10 +178,9 @@ export default function MemberDashboard() {
|
||||
|
||||
switch (item.product.type) {
|
||||
case "consulting": {
|
||||
// Only show if user has a confirmed upcoming consulting slot for this product
|
||||
// Only show if user has a confirmed upcoming consulting slot
|
||||
const upcomingSlot = consultingSlots.find(
|
||||
(slot) =>
|
||||
slot.product_id === item.product.id &&
|
||||
slot.status === "confirmed" &&
|
||||
new Date(slot.date) >= new Date(now.setHours(0, 0, 0, 0))
|
||||
);
|
||||
@@ -350,7 +349,7 @@ export default function MemberDashboard() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-4">
|
||||
<Badge className={order.payment_status === "paid" ? "bg-brand-accent text-white" : "bg-amber-500 text-white"} rounded-full>
|
||||
<Badge className={order.payment_status === "paid" ? "bg-brand-accent text-white rounded-full" : "bg-amber-500 text-white rounded-full"}>
|
||||
{order.payment_status === "paid" ? "Lunas" : "Pending"}
|
||||
</Badge>
|
||||
<span className="font-bold">{formatIDR(order.total_amount)}</span>
|
||||
|
||||
Reference in New Issue
Block a user