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>
9.1 KiB
Adilo Video Player - Quick AI Agent Reference
For Your Windsurf/IDE AI Agent
Copy this into your .codebase instructions or share with AI agent:
Project: LearnHub - Adilo M3U8 Video Player with Custom Chapters
Problem Statement
Build a React video player that:
- Streams video from Adilo using M3U8 (HLS) direct URL
- Displays custom chapter navigation
- Allows click-to-jump to chapters
- Tracks user progress
- Saves completion data to Supabase
Tech Stack
- React 18+ (Hooks, Context)
- HLS.js - for M3U8 streaming
- Supabase - for progress tracking
- HTML5 Video API - native controls
- CSS Modules - styling
Quick Command Reference
Install Dependencies
npm install hls.js @supabase/supabase-js
Project Structure to Create
src/
├── components/
│ ├── AdiloVideoPlayer.jsx # Main component
│ ├── ChapterNavigation.jsx # Chapter sidebar
│ └── ProgressBar.jsx # Progress indicator
├── hooks/
│ ├── useAdiloPlayer.js # HLS streaming logic
│ └── useChapterTracking.js # Chapter tracking
├── services/
│ ├── adiloService.js # Adilo API calls
│ └── progressService.js # Supabase progress
├── styles/
│ └── AdiloVideoPlayer.module.css
└── types/
└── video.types.js
Implementation Phases (In Order)
⭐ PHASE 1: useAdiloPlayer Hook
Goal: Get HLS.js working with M3U8 URL
What to build:
- React hook that initializes HLS.js instance
- Return: videoRef, isReady, isPlaying, currentTime, duration
- Handle browser compatibility (Safari vs HLS.js)
- Clean up HLS instance on unmount
- Emit callbacks: onTimeUpdate, onEnded, onError
Test with:
const { videoRef, currentTime, isReady } = useAdiloPlayer({
m3u8Url: "https://adilo.bigcommand.com/m3u8/...",
autoplay: false,
onTimeUpdate: (time) => console.log(time)
});
⭐ PHASE 2: useChapterTracking Hook
Goal: Determine which chapter is currently active
What to build:
- React hook that tracks active chapter
- Input: chapters array, currentTime
- Return: activeChapter, activeChapterId, chapterProgress
- Detect chapter transitions
- Calculate progress percentage
Chapter data structure:
{
id: "ch1",
startTime: 0,
endTime: 120,
title: "Introduction",
description: "Welcome to the course"
}
Test with:
const { activeChapter, chapterProgress } = useChapterTracking({
chapters: [...],
currentTime: 45
});
// activeChapter should be chapter with startTime ≤ 45 < endTime
⭐ PHASE 3: AdiloVideoPlayer Component
Goal: Main player combining both hooks
What to build:
- Component that uses both hooks
- Renders:
- Props: m3u8Url, videoId, chapters, autoplay, showChapters
- Methods: jumpToChapter(), play(), pause()
- Callbacks: onChapterChange, onVideoComplete, onProgressUpdate
Usage example:
<AdiloVideoPlayer
m3u8Url="https://adilo.bigcommand.com/m3u8/..."
chapters={[{id: "1", startTime: 0, endTime: 120, title: "Intro"}]}
onVideoComplete={() => markComplete()}
onChapterChange={(ch) => console.log(ch.title)}
/>
⭐ PHASE 4: ChapterNavigation Component
Goal: Display chapters user can click to jump
What to build:
- Sidebar/timeline showing all chapters
- Highlight current active chapter
- Show time for each chapter
- Click handler to jump to chapter
- Progress bar for each chapter
Props:
- chapters: Chapter[]
- activeChapterId: string
- currentTime: number
- onChapterClick: (startTime: number) => void
- completedChapters: string[]
⭐ PHASE 5: Supabase Integration
Goal: Save video progress to database
Database schema needed:
CREATE TABLE video_progress (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
user_id uuid NOT NULL,
video_id uuid NOT NULL,
last_position int,
completed_chapters text[],
watched_percentage int,
is_completed boolean DEFAULT false,
completed_at timestamp,
created_at timestamp DEFAULT now(),
updated_at timestamp DEFAULT now(),
UNIQUE(user_id, video_id)
);
Functions to implement:
saveProgress(userId, videoId, currentTime, completedChapters)getLastPosition(userId, videoId)- resume from last positionmarkVideoComplete(userId, videoId)getVideoAnalytics(userId, videoId)
⭐ PHASE 6: Styling
Goal: Make it look good and responsive
Key CSS classes needed:
.adilo-player- main container.video-container- video wrapper.chapters-sidebar- chapter list.chapter-item- individual chapter.chapter-item.active- highlight active.progress-bar- progress visualization
Responsive breakpoints:
- Desktop: sidebar on right
- Tablet: sidebar below video
- Mobile: horizontal timeline under video
Key Implementation Details
HLS.js Initialization Pattern
if (Hls.isSupported()) {
const hls = new Hls();
hls.loadSource(m3u8Url);
hls.attachMedia(videoElement);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
videoElement.play();
});
} else if (videoElement.canPlayType('application/vnd.apple.mpegurl')) {
// Safari native HLS support
videoElement.src = m3u8Url;
}
Chapter Jump Implementation
const jumpToChapter = (startTime) => {
if (videoRef.current) {
videoRef.current.currentTime = startTime;
videoRef.current.play();
}
};
Track Current Chapter Pattern
const current = chapters.find(
ch => currentTime >= ch.startTime && currentTime < ch.endTime
);
setActiveChapter(current?.id);
Debounce Progress Saves
// Save progress every 5 seconds, not on every timeupdate
const saveProgressDebounced = debounce(
(userId, videoId, time) => saveProgress(userId, videoId, time),
5000
);
Common Tasks for AI Agent
When asking your AI agent to implement:
Task: "Create useAdiloPlayer hook"
Should generate:
- Import HLS from 'hls.js'
- useRef for video element
- useEffect to initialize HLS
- useCallback for event handlers
- Clean up logic in return
Task: "Add chapter jump functionality"
Should implement:
- Button click handler
- Call jumpToChapter(startTime)
- Update videoRef.current.currentTime
- Play the video
Task: "Save progress to Supabase"
Should implement:
- Create/update row in video_progress table
- Include: user_id, video_id, last_position, completed_chapters
- Handle conflicts (UPSERT)
- Error handling
Task: "Make chapters responsive"
Should implement:
- CSS Grid for desktop (sidebar)
- Flex column for mobile
- Media query at 768px breakpoint
- Adjust spacing and font sizes
Testing Checklist
Unit Tests
- useAdiloPlayer hook returns correct refs/values
- useChapterTracking calculates active chapter correctly
- jumpToChapter updates video.currentTime
- Progress saves to Supabase
Integration Tests
- Video plays when component mounts
- Chapter changes highlight in UI
- Clicking chapter jumps player
- Progress saves on interval
- Completion triggers callback
Browser Tests
- Works on Chrome/Edge (HLS.js)
- Works on Firefox (HLS.js)
- Works on Safari (native HLS)
- Works on mobile browsers
Edge Cases
- Bad M3U8 URL shows error
- Network interruption handled
- Video paused mid-chapter
- Page refresh preserves position
Environment Variables Needed
VITE_SUPABASE_URL=your_supabase_url
VITE_SUPABASE_ANON_KEY=your_supabase_key
Debugging Tips
HLS.js not loading?
- Check M3U8 URL is correct from Adilo
- Verify CORS headers from Adilo
- Check browser console for HLS.js errors
- Try
hls.on(Hls.Events.ERROR, console.error)
Chapter not highlighting?
- Add console.log(currentTime, chapters) to track values
- Verify chapter startTime/endTime are correct
- Check activeChapter state is updating
Progress not saving?
- Verify Supabase connection works
- Check user_id and video_id are defined
- Add error logs to saveProgress function
- Check database table schema matches
Performance Optimization Tips
-
Memoize chapters list to prevent re-renders
const chapters = useMemo(() => chaptersData, [chaptersData]); -
Debounce timeupdate events (fires 60x per second!)
const updateChapter = debounce(() => {...}, 100); video.addEventListener('timeupdate', updateChapter); -
Lazy load chapter images/thumbnails
<img loading="lazy" src={chapter.thumbnail} /> -
Use React.memo for ChapterNavigation
export default React.memo(ChapterNavigation);
Resources
- HLS.js Docs: https://github.com/video-dev/hls.js/wiki
- HTML5 Video API: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video
- Supabase JS Client: https://supabase.com/docs/reference/javascript/introduction
Status: Ready to implement! 🚀
Start with PHASE 1 (useAdiloPlayer hook), then PHASE 2-6 in order.