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:
557
adilo-player-impl-plan.md
Normal file
557
adilo-player-impl-plan.md
Normal 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!**
|
||||
Reference in New Issue
Block a user