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

557
adilo-player-impl-plan.md Normal file
View File

@@ -0,0 +1,557 @@
# Adilo Custom Video Player with Chapter System - Implementation Plan
## Project Overview
Build a React video player component that uses Adilo's M3U8 streaming URL with custom chapter navigation system for LearnHub LMS.
---
## Architecture
### Components Structure
```
VideoLesson/
├── AdiloVideoPlayer.jsx (Main player component)
├── ChapterNavigation.jsx (Chapter sidebar/timeline)
├── VideoControls.jsx (Custom controls - optional)
└── hooks/
├── useAdiloPlayer.js (HLS player logic)
└── useChapterTracking.js (Chapter progress tracking)
```
### Data Flow
```
Adilo M3U8 URL
HLS.js (streaming)
HTML5 <video> element
currentTime tracking
Chapter UI sync
Supabase (save progress)
```
---
## Step-by-Step Implementation
### PHASE 1: Dependencies Setup
**Install required packages:**
```bash
npm install hls.js
npm install @supabase/supabase-js # For progress tracking
```
**No additional UI library needed** - use native HTML5 video + your own CSS for chapters.
---
### PHASE 2: Core Hook - useAdiloPlayer
**File: `hooks/useAdiloPlayer.js`**
**Purpose:** Handle HLS streaming with HLS.js library
**Responsibilities:**
- Initialize HLS instance
- Load M3U8 URL
- Handle browser compatibility (Safari native HLS vs HLS.js)
- Expose video element ref for external control
- Emit events (play, pause, ended, timeupdate)
**Function signature:**
```javascript
const {
videoRef,
isReady,
isPlaying,
currentTime,
duration,
error
} = useAdiloPlayer({
m3u8Url: string,
autoplay: boolean,
onTimeUpdate: (time: number) => void,
onEnded: () => void,
onError: (error) => void
})
```
**Key features:**
- Auto-dispose HLS instance on unmount
- Handle loading states
- Error boundary for failed streams
- Track play/pause states
---
### PHASE 3: Core Hook - useChapterTracking
**File: `hooks/useChapterTracking.js`**
**Purpose:** Track which chapter user is currently viewing
**Responsibilities:**
- Determine active chapter from currentTime
- Calculate chapter progress percentage
- Detect chapter transitions
- Export chapter completion data
**Function signature:**
```javascript
const {
activeChapter,
activeChapterId,
chapterProgress, // 0-100%
completedChapters,
chapterTimeline // for progress bar
} = useChapterTracking({
chapters: Chapter[],
currentTime: number,
onChapterChange: (chapter) => void
})
```
**Chapter object structure:**
```javascript
{
id: string,
startTime: number, // in seconds
endTime: number, // in seconds
title: string,
description?: string, // optional
thumbnail?: string // optional
}
```
---
### PHASE 4: Main Component - AdiloVideoPlayer
**File: `components/AdiloVideoPlayer.jsx`**
**Purpose:** Main video player component that combines HLS streaming + chapter tracking
**Props:**
```javascript
{
m3u8Url: string, // From Adilo dashboard
videoId: string, // For database tracking
chapters: Chapter[], // Your chapter data
autoplay: boolean, // Default: false
showChapters: boolean, // Default: true
onVideoComplete: (data) => void, // Callback when video ends
onChapterChange: (chapter) => void,
onProgressUpdate: (progress) => void
}
```
**Component structure:**
```jsx
<div className="adilo-player">
{/* Video container */}
<div className="video-container">
<video
ref={videoRef}
controls
controlsList="nodownload"
/>
{/* Loading indicator */}
{!isReady && <LoadingSpinner />}
</div>
{/* Chapter Navigation */}
{showChapters && (
<ChapterNavigation
chapters={chapters}
activeChapterId={activeChapterId}
currentTime={currentTime}
onChapterClick={jumpToChapter}
completedChapters={completedChapters}
/>
)}
{/* Progress bar (optional) */}
<ProgressBar
chapters={chapters}
currentTime={currentTime}
/>
</div>
```
**Key methods:**
- `jumpToChapter(startTime)` - Seek to chapter
- `play()` / `pause()` - Control playback
- `getCurrentProgress()` - Get session progress
---
### PHASE 5: Chapter Navigation Component
**File: `components/ChapterNavigation.jsx`**
**Purpose:** Display chapters as sidebar/timeline with click-to-jump
**Layout options:**
1. **Sidebar** - Vertical list on side (desktop)
2. **Horizontal** - Timeline below video (mobile)
3. **Collapsible** - Toggle on mobile
**Features:**
- Show current/upcoming chapters
- Highlight active chapter
- Show time remaining for current chapter
- Progress indicators
- Drag-to-seek on timeline (optional)
**Chapter item structure:**
```jsx
<div className="chapter-item">
<div className="chapter-time">{formatTime(startTime)}</div>
<div className="chapter-title">{title}</div>
<div className="chapter-progress">{progressBar}</div>
<button onClick={() => jumpToChapter(startTime)}>
Jump to Chapter
</button>
</div>
```
---
### PHASE 6: Supabase Integration (Optional)
**File: `services/progressService.js`**
**Purpose:** Save video progress to database
**Database table structure:**
```sql
CREATE TABLE video_progress (
id uuid PRIMARY KEY,
user_id uuid NOT NULL,
video_id uuid NOT NULL,
last_position int, -- seconds
completed_chapters text[], -- array of chapter IDs
watched_percentage int, -- 0-100
is_completed boolean,
completed_at timestamp,
created_at timestamp,
updated_at timestamp,
UNIQUE(user_id, video_id)
)
```
**Functions to implement:**
```javascript
// Save current progress
saveProgress(userId, videoId, currentTime, completedChapters)
// Resume from last position
getLastPosition(userId, videoId)
// Mark video as complete
markVideoComplete(userId, videoId)
// Get completion analytics
getVideoAnalytics(userId, videoId)
```
---
### PHASE 7: Styling
**File: `styles/AdiloVideoPlayer.module.css` or your preferred CSS approach**
**Key styles needed:**
```css
/* Video container */
.video-container {
position: relative;
width: 100%;
aspect-ratio: 16/9;
background: #000;
}
video {
width: 100%;
height: 100%;
}
/* Chapter sidebar */
.chapters-sidebar {
background: #f5f5f5;
padding: 16px;
max-height: 400px;
overflow-y: auto;
}
.chapter-item {
padding: 12px;
border-radius: 8px;
cursor: pointer;
transition: background 0.2s;
}
.chapter-item.active {
background: #e0f2fe;
border-left: 4px solid #0ea5e9;
}
.chapter-time {
font-weight: 600;
color: #333;
}
.chapter-title {
font-size: 14px;
color: #666;
margin-top: 4px;
}
/* Progress bar */
.progress-bar {
display: flex;
gap: 2px;
height: 4px;
background: #e5e5e5;
border-radius: 2px;
}
.progress-segment {
background: #0ea5e9;
flex: 1;
border-radius: 1px;
}
.progress-segment.completed {
background: #10b981;
}
/* Responsive */
@media (max-width: 768px) {
.chapters-sidebar {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
}
```
---
## Implementation Checklist
### Setup Phase
- [ ] Install dependencies (hls.js, @supabase/supabase-js)
- [ ] Set up folder structure
- [ ] Configure Supabase client (if using progress tracking)
### Core Development
- [ ] Implement `useAdiloPlayer` hook
- [ ] Implement `useChapterTracking` hook
- [ ] Create `AdiloVideoPlayer` component
- [ ] Create `ChapterNavigation` component
- [ ] Add styling/CSS
### Integration
- [ ] Implement Supabase progress service
- [ ] Add error handling & loading states
- [ ] Add accessibility features (ARIA labels, keyboard navigation)
- [ ] Test HLS streaming on different browsers
### Testing
- [ ] Test video playback (Chrome, Firefox, Safari, mobile)
- [ ] Test chapter navigation
- [ ] Test progress saving to Supabase
- [ ] Test responsive design
- [ ] Test error scenarios (bad M3U8 URL, network issues)
### Optional Enhancements
- [ ] Add playback speed control
- [ ] Add quality selector (if HLS variants available)
- [ ] Add full-screen mode
- [ ] Add picture-in-picture
- [ ] Add watch history
- [ ] Add completion badges
---
## Code Example Template
### Main Usage in LearnHub
```jsx
import AdiloVideoPlayer from '@/components/AdiloVideoPlayer';
function LessonPage({ lessonId }) {
const [lesson, setLesson] = useState(null);
const [userProgress, setUserProgress] = useState(null);
const user = useAuth().user;
useEffect(() => {
// Fetch lesson with chapters
fetchLessonData(lessonId).then(setLesson);
// Get user's last progress
getLastPosition(user.id, lessonId).then(setUserProgress);
}, [lessonId]);
const handleChapterChange = (chapter) => {
console.log(`Chapter changed: ${chapter.title}`);
};
const handleVideoComplete = (data) => {
// Mark as complete in Supabase
markVideoComplete(user.id, lessonId);
// Show completion message
toast.success('Lesson completed! 🎉');
};
const handleProgressUpdate = (progress) => {
// Save progress every 10 seconds
if (progress.currentTime % 10 === 0) {
saveProgress(user.id, lessonId, progress.currentTime, progress.completedChapters);
}
};
if (!lesson) return <LoadingPage />;
return (
<div className="lesson-container">
<h1>{lesson.title}</h1>
<AdiloVideoPlayer
m3u8Url={lesson.m3u8Url}
videoId={lesson.id}
chapters={lesson.chapters}
autoplay={false}
showChapters={true}
onChapterChange={handleChapterChange}
onVideoComplete={handleVideoComplete}
onProgressUpdate={handleProgressUpdate}
/>
<div className="lesson-content">
<h2>Lesson Details</h2>
<p>{lesson.description}</p>
</div>
</div>
);
}
export default LessonPage;
```
---
## Important Notes
### Video URL Storage
Store the M3U8 URL in your Supabase `videos` or `lessons` table:
```sql
CREATE TABLE lessons (
id uuid PRIMARY KEY,
title text,
description text,
m3u8_url text, -- Store the Adilo M3U8 URL here
chapters jsonb, -- Store chapters as JSON array
created_at timestamp
)
```
### Security Considerations
-**Don't expose M3U8 URL in frontend code** - fetch from backend
-**Validate M3U8 URLs** - only allow Adilo domains
-**Use CORS headers** - ensure Adilo allows cross-origin requests
-**Log access** - track who watches which videos
### Browser Compatibility
-**Chrome/Edge**: HLS.js library
-**Firefox**: HLS.js library
-**Safari**: Native HLS support (no library needed)
-**Mobile browsers**: Auto-detects capability
### Performance Tips
- 🚀 Lazy load chapter data
- 🚀 Debounce progress updates
- 🚀 Memoize chapter calculations
- 🚀 Use video preload="metadata"
---
## Common Pitfalls to Avoid
1. **❌ Don't forget to dispose HLS instance** - Memory leak
- ✅ Do: Clean up in useEffect return
2. **❌ Don't update state on every timeupdate** - Performance issue
- ✅ Do: Debounce or throttle updates
3. **❌ Don't hardcode M3U8 URLs in component** - Security issue
- ✅ Do: Fetch from backend API
4. **❌ Don't assume HLS.js works everywhere** - Safari native support exists
- ✅ Do: Check `Hls.isSupported()` and fallback
5. **❌ Don't forget CORS headers** - Cross-origin requests fail
- ✅ Do: Verify Adilo allows your domain
---
## Testing Commands
```bash
# Install dev dependencies
npm install --save-dev @testing-library/react @testing-library/jest-dom
# Run tests
npm test
# Build for production
npm run build
# Check bundle size
npm run analyze
```
---
## Next Steps
1. **Get M3U8 URL** from Adilo dashboard ✅ (You found it!)
2. **Store URL** in your Supabase lessons table
3. **Create the hooks** (start with `useAdiloPlayer`)
4. **Build the component** (AdiloVideoPlayer)
5. **Integrate chapters** (ChapterNavigation)
6. **Add progress tracking** (Supabase integration)
7. **Style and polish** (CSS/responsive design)
8. **Test thoroughly** (all browsers, mobile, edge cases)
---
## Files to Create
```
src/
├── components/
│ ├── AdiloVideoPlayer.jsx
│ ├── ChapterNavigation.jsx
│ └── ProgressBar.jsx
├── hooks/
│ ├── useAdiloPlayer.js
│ └── useChapterTracking.js
├── services/
│ ├── progressService.js
│ └── adiloService.js
├── styles/
│ └── AdiloVideoPlayer.module.css
└── types/
└── video.types.js
```
---
**Ready to implement? Start with Phase 1 & 2 (setup + useAdiloPlayer hook). Let me know if you need help with any specific phase!**