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:
dwindown
2026-01-01 23:54:32 +07:00
parent 41f7b797e7
commit 60baf32f73
29 changed files with 3694 additions and 35048 deletions

View File

@@ -0,0 +1,94 @@
-- Fix consulting_slots table: ensure user_id column exists, backfill from orders, and add RLS policies
-- This fixes the 400 error when members try to fetch their consulting slots
-- Add user_id column if it doesn't exist
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = 'consulting_slots'
AND column_name = 'user_id'
) THEN
ALTER TABLE consulting_slots
ADD COLUMN user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE;
RAISE NOTICE 'user_id column added to consulting_slots';
ELSE
RAISE NOTICE 'user_id column already exists in consulting_slots';
END IF;
END $$;
-- Backfill user_id from orders for existing records
DO $$
DECLARE
backfill_count INTEGER;
null_count INTEGER;
BEGIN
-- Count NULL user_ids before backfill
SELECT COUNT(*) INTO null_count FROM consulting_slots WHERE user_id IS NULL;
RAISE NOTICE 'Found % consulting_slots with NULL user_id', null_count;
-- Backfill from orders
UPDATE consulting_slots cs
SET user_id = o.user_id
FROM orders o
WHERE cs.order_id = o.id
AND cs.user_id IS NULL;
GET DIAGNOSTICS backfill_count = ROW_COUNT;
RAISE NOTICE 'Backfilled user_id for % consulting_slots from orders', backfill_count;
-- Check remaining NULLs
SELECT COUNT(*) INTO null_count FROM consulting_slots WHERE user_id IS NULL;
RAISE NOTICE 'Remaining consulting_slots with NULL user_id: %', null_count;
END $$;
-- Create index for faster queries
CREATE INDEX IF NOT EXISTS idx_consulting_slots_user_id ON consulting_slots(user_id);
CREATE INDEX IF NOT EXISTS idx_consulting_slots_user_status ON consulting_slots(user_id, status);
-- Enable RLS on consulting_slots (if not already enabled)
ALTER TABLE consulting_slots ENABLE ROW LEVEL SECURITY;
-- Drop ALL existing policies first to avoid conflicts
DROP POLICY IF EXISTS "consulting_slots_select_own" ON consulting_slots;
DROP POLICY IF EXISTS "consulting_slots_insert_own" ON consulting_slots;
DROP POLICY IF EXISTS "consulting_slots_update_own" ON consulting_slots;
DROP POLICY IF EXISTS "consulting_slots_select_all" ON consulting_slots;
DROP POLICY IF EXISTS "consulting_slots_service_role" ON consulting_slots;
-- Create RLS policies for consulting_slots
-- Policy for users to see their own slots
CREATE POLICY "consulting_slots_select_own"
ON consulting_slots
FOR SELECT
TO authenticated
USING (auth.uid() = user_id);
-- Policy for users to insert their own slots
CREATE POLICY "consulting_slots_insert_own"
ON consulting_slots
FOR INSERT
TO authenticated
WITH CHECK (auth.uid() = user_id);
-- Policy for users to update their own slots
CREATE POLICY "consulting_slots_update_own"
ON consulting_slots
FOR UPDATE
TO authenticated
USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Policy for service role (admins) to access all slots
CREATE POLICY "consulting_slots_service_role"
ON consulting_slots
FOR ALL
TO service_role
USING (true)
WITH CHECK (true);
-- Grant permissions
GRANT USAGE ON SCHEMA public TO service_role;
GRANT ALL ON consulting_slots TO service_role;
GRANT SELECT, INSERT, UPDATE, DELETE ON consulting_slots TO authenticated;