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>
Added robust error handling for Plyr player instance:
- Check if player.on method exists before using events
- Fallback to polling time updates every 500ms if events unavailable
- Use setInterval to wait for player initialization
- Properly cleanup intervals on component unmount
- Prevents "L.on is not a function" error
This ensures the video player works even if Plyr's event system
has issues initializing, using a reliable polling fallback.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Rewrote VideoPlayerWithChapters component using plyr-react with best practices:
- Use Plyr React component wrapper for proper integration
- Aggressive YouTube UI hiding with correct parameters (controls=0, disablekb=1, fs=0)
- Block right-click context menu globally
- Block developer tools shortcuts (F12, Ctrl+Shift+I/J, Ctrl+U)
- CSS pointer-events manipulation to block YouTube iframe interactions
- Only Plyr controls remain interactive
- Accurate time tracking via Plyr's timeupdate event
- Chapter jump functionality via Plyr's currentTime API
- Custom accent color support for Plyr controls
- Hide YouTube's native play button with ::after overlay
This implementation provides:
✅ Working timeline with accurate duration tracking
✅ Chapter navigation that jumps to correct timestamps
✅ Maximum prevention of YouTube UI access
✅ Custom Plyr controls with your accent color
✅ Right-click and dev tools blocking
✅ No "Watch on YouTube" or copy link buttons
✅ Clean user experience
Based on recommended plyr-react implementation patterns.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implemented multiple overlay divs to prevent access to YouTube branding:
- Top overlay blocks "Watch on YouTube" button and title overlay
- Bottom-right overlay blocks YouTube logo
- Full-screen overlay with context menu prevention blocks right-click
- Set controls=0 parameter to hide YouTube controls completely
- Keep YouTube API working for time tracking and chapter navigation
This prevents users from:
- Clicking "Watch on YouTube" button
- Seeing YouTube logo
- Copying video URL via context menu
- Accessing YouTube native controls
While maintaining:
- Accurate time tracking via getCurrentTime()
- Chapter jump functionality via seekTo()
- Custom timeline with chapters
- Fullscreen capability
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replaces Plyr-based implementation with native YouTube IFrame Player API to fix:
- Accurate time tracking via getCurrentTime() polling every 500ms
- Chapter jump functionality using seekTo() and playVideo() API calls
- Right-click prevention with transparent overlay
- Proper chapter highlighting based on current playback time
Technical changes:
- Load YouTube IFrame API script on component mount
- Create YT.Player instance for programmatic control
- Poll getCurrentTime() in interval for real-time tracking
- Use getPlayerById() to retrieve player for jumpToTime operations
- Add pointer-events: none overlay with context menu prevention
- Generate unique iframe IDs for proper API targeting
This approach balances working timeline/jump functionality with preventing
direct URL access via overlay blocking right-click context menus.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Complete rewrite using YouTube's native iframe embed instead of Plyr + YouTube API.
This fixes all the bugs caused by API conflicts.
Changes:
- Remove Plyr library dependency
- Remove YouTube IFrame API script loading
- Use native YouTube iframe with enablejsapi=1
- Track time via postMessage events (infoDelivery)
- Jump to time via postMessage commands
- Remove all strategic overlays (not needed with native controls)
- Remove custom CSS for hiding YouTube elements
- Simple, clean iframe wrapper with 16:9 aspect ratio
- Enable YouTube's native controls (controls=1)
- Remove sandbox attribute that was blocking features
This approach:
✅ Fixes fullscreen permissions error
✅ Time tracking works reliably
✅ Chapter jump works via postMessage
✅ No overlays blocking controls
✅ Native YouTube controls available
✅ Much simpler code
The trade-off is that users can access YouTube UI, but this is better than
a broken player. The original goal of blocking YouTube UI is not achievable
without introducing significant bugs and complexity.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Fixed issues:
1. Duration now displays correctly using Plyr's time tracking instead of YouTube API
2. Chapter jump functionality now works using Plyr's currentTime property
3. Overlays now properly avoid Plyr controls - only show when paused/ended
4. Hidden YouTube's native play button with CSS
Key changes:
- Use Plyr's native time tracking (player.currentTime) instead of YouTube API
- Jump to time uses Plyr's seek (player.currentTime = time, then play)
- Top overlay only appears when paused/ended (not when playing)
- Paused/ended overlays start at bottom: 80px to avoid Plyr controls
- Added CSS to hide .ytp-large-play-button and .html5-video-player background
- Moved time tracking to onReady callback to ensure player is initialized
- Removed dependencies on chapters from useEffect to prevent re-initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Based on web research, implemented a comprehensive solution to prevent
users from accessing YouTube UI elements and copying/sharing video URLs.
Changes:
- Load YouTube IFrame Player API to track player state accurately
- Create YT Player instance to detect play/pause/end events
- Add strategic overlays to block specific YouTube UI elements:
- Top overlay (60px) - blocks "Copy Link" and title (always on)
- Bottom right overlay - blocks YouTube logo (always on)
- Bottom left overlay - blocks "Watch on YouTube" (before start)
- Center overlay - blocks "More Videos" (when paused)
- Large overlay - blocks related videos wall (when ended)
- Add sandbox attribute to iframe to prevent popups
- Track player state: -1=unstarted, 0=ended, 1=playing, 2=paused, 3=buffering, 5=cued
- All overlays prevent context menu and clicks with preventDefault
This approach is based on successful implementations found in:
- Medium article "How We Safely Embed YouTube Videos"
- xFanatical Safe Doc approach for educational content
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Key changes:
- Create .plyr__poster element manually (Plyr doesn't auto-create for YouTube)
- Set z-index: 10 and pointer-events: auto when visible
- Add 500ms delay before hiding poster on play (matches reference)
- Clear timeout if paused before poster fully hides
- poster blocks YouTube UI when paused/initial, fades out when playing
This matches the reference implementation behavior exactly.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Instead of custom overlay, use Plyr's .plyr__poster element which:
- Naturally covers the iframe when paused/initial load
- Allows Plyr controls to receive clicks (z-index hierarchy)
- Hides (opacity: 0) when playing, shows (opacity: 1) when paused
- Blocks YouTube UI interactions when visible
This matches the reference implementation behavior.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Track playback state and show overlay with pointer-events:auto when paused,
pointer-events:none when playing. This blocks YouTube UI (copy link, logo)
when video is paused or initially loaded, while allowing Plyr controls.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed overlay to use pointer-events: none instead of pointer-events-auto.
This allows Plyr controls to receive clicks while still blocking YouTube iframe interactions.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Changes
### Video Player CSS Overlay Fix
- Moved overlay inside plyr__video-embed container
- Added check for plyr__control--overlaid (play button)
- Now only blocks YouTube iframe, not Plyr controls
- Preserves full functionality of play button, progress bar, etc.
### Site Settings Permissions
- Added RLS policy to make site_settings publicly readable
- All authenticated users can now access brand_accent_color
- Fixes 404 error when members try to fetch accent color
## Technical Details
- Overlay now properly allows clicks through to Plyr controls
- Site settings now accessible via public RLS policy for authenticated users
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
## Changes
### Chapter Time Format Support
- Added HH:MM:SS format support in addition to MM:SS
- Updated time parsing to handle variable-length inputs
- Updated time display formatter to show hours when > 0
- Updated input placeholder and validation pattern
- Updated help text to mention both formats
### Video Player Improvements
- Added fullscreen button to Plyr controls
- Added quality/settings control to Plyr controls
- Fixed accent color theming with !important CSS rules
- Removed hardcoded default accent color (#f97316)
- Updated Bootcamp and WebinarRecording pages to use empty string initial state
### Access Control & Security
- Added transparent CSS overlay to block YouTube UI interactions
- Disabled YouTube native controls (controls=0, disablekb=1, fs=0)
- Set iframe pointer-events-none to prevent direct interaction
- Prevents members from copying/sharing YouTube URLs directly
- Preserves Plyr controls functionality through click handler
### Files Modified
- src/components/admin/ChaptersEditor.tsx: Time format HH:MM:SS support
- src/components/VideoPlayerWithChapters.tsx: Security overlay & theming fixes
- src/pages/Bootcamp.tsx: Accent color initialization fix
- src/pages/WebinarRecording.tsx: Accent color initialization fix
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement timeline chapters for webinar and bootcamp videos with click-to-jump functionality:
**Components:**
- VideoPlayerWithChapters: Plyr.io-based player with chapter support
- TimelineChapters: Clickable chapter markers with active state
- ChaptersEditor: Admin UI for managing video chapters
**Features:**
- YouTube videos: Clickable timestamps that jump to specific time
- Embed videos: Static timeline display (non-clickable)
- Real-time chapter tracking during playback
- Admin-defined accent color for Plyr theme
- Auto-hides timeline when no chapters configured
**Database:**
- Add chapters JSONB column to products table (webinars)
- Add chapters JSONB column to bootcamp_lessons table
- Create indexes for faster queries
**Updated Pages:**
- WebinarRecording: Two-column layout (video + timeline)
- Bootcamp: Per-lesson chapter support
- AdminProducts: Chapter editor for webinars
- CurriculumEditor: Chapter editor for lessons
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>