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:
372
adilo-ai-agent-quick-ref.md
Normal file
372
adilo-ai-agent-quick-ref.md
Normal file
@@ -0,0 +1,372 @@
|
||||
# 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
|
||||
```bash
|
||||
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:**
|
||||
```javascript
|
||||
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:**
|
||||
```javascript
|
||||
{
|
||||
id: "ch1",
|
||||
startTime: 0,
|
||||
endTime: 120,
|
||||
title: "Introduction",
|
||||
description: "Welcome to the course"
|
||||
}
|
||||
```
|
||||
|
||||
**Test with:**
|
||||
```javascript
|
||||
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: <video> element + video controls
|
||||
- Props: m3u8Url, videoId, chapters, autoplay, showChapters
|
||||
- Methods: jumpToChapter(), play(), pause()
|
||||
- Callbacks: onChapterChange, onVideoComplete, onProgressUpdate
|
||||
|
||||
**Usage example:**
|
||||
```jsx
|
||||
<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:**
|
||||
```sql
|
||||
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 position
|
||||
- `markVideoComplete(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
|
||||
```javascript
|
||||
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
|
||||
```javascript
|
||||
const jumpToChapter = (startTime) => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.currentTime = startTime;
|
||||
videoRef.current.play();
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### Track Current Chapter Pattern
|
||||
```javascript
|
||||
const current = chapters.find(
|
||||
ch => currentTime >= ch.startTime && currentTime < ch.endTime
|
||||
);
|
||||
setActiveChapter(current?.id);
|
||||
```
|
||||
|
||||
### Debounce Progress Saves
|
||||
```javascript
|
||||
// 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
|
||||
|
||||
1. **Memoize chapters list** to prevent re-renders
|
||||
```javascript
|
||||
const chapters = useMemo(() => chaptersData, [chaptersData]);
|
||||
```
|
||||
|
||||
2. **Debounce timeupdate events** (fires 60x per second!)
|
||||
```javascript
|
||||
const updateChapter = debounce(() => {...}, 100);
|
||||
video.addEventListener('timeupdate', updateChapter);
|
||||
```
|
||||
|
||||
3. **Lazy load chapter images/thumbnails**
|
||||
```javascript
|
||||
<img loading="lazy" src={chapter.thumbnail} />
|
||||
```
|
||||
|
||||
4. **Use React.memo for ChapterNavigation**
|
||||
```javascript
|
||||
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.
|
||||
Reference in New Issue
Block a user