Fixed the "Identifier 'xL' has already been declared" error that occurred
in production builds by switching from SWC minifier to Terser.
Changes:
- Updated vite.config.ts to explicitly use Terser minifier
- Added terser as dev dependency
- Configured safe Terser options to prevent variable name conflicts
- Disabled unsafe optimizations that can cause identifier collisions
This resolves the blank page issue in production while maintaining
bundle size optimization.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Added a "Kembali ke Beranda" (Back to Home) button on the login/signup
page to allow users to navigate back to the home page without needing
to authenticate.
Changes:
- Imported Link and ArrowLeft icon from lucide-react
- Added button above the auth card that links to "/"
- Wrapped content in a space-y-4 container for proper spacing
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
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>
- Update migration to use correct table name (platform_settings, not site_settings)
- Fix WebinarRecording.tsx to query platform_settings table
- Fix Bootcamp.tsx to query platform_settings table
- This allows authenticated users to access brand_accent_color for theming
🤖 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
- Added max-height of 400px to chapter list container
- Added overflow-y-auto for vertical scrolling
- Added custom scrollbar styling (thin, with hover effects)
- Added pr-2 padding for scrollbar space
## Benefits
- Prevents timeline from becoming too tall for long videos
- Maintains consistent layout regardless of chapter count
- Better UX for 2-3+ hour videos with many chapters
- Smooth scrolling with styled scrollbar
🤖 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>
Fixed issues:
- Added isAvailable parameter to handleSlotClick to prevent clicks on unavailable slots
- Added validation to prevent selecting past dates in date picker
- Added validation in handlePreviousDay to block navigating to past dates
- Added warning message when trying to view past dates
- Prevents the "slot is not defined" error when clicking disabled slots
Now the modal properly:
- Blocks selecting dates before today
- Shows clear error messages for past dates
- Prevents clicks on unavailable time slots
- Handles edge cases like passed sessions being rescheduled
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Enhanced TimeSlotPickerModal with:
- Date selection via date input and navigation arrows
- Passed time slots filtered out (only show future slots for today)
- Complete schedule picker (date + time in one modal)
- Dynamic slot availability based on selected date
- Better UX with date/time sections clearly separated
Updated AdminConsulting to:
- Use editSessionDate when opening time slot picker
- Pass selected date back from modal to parent
- Handle date changes during rescheduling
Fixes the issue where admins could only select time slots for the original
session date, not the new rescheduled date.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: Admins need to reschedule sessions when members can't make it,
either before or after the scheduled time. Previously had to edit
manually without clear indication of rescheduling vs simple edits.
Solution:
1. Add "Reschedule" button (blue Calendar icon) for confirmed sessions:
- In desktop table action buttons
- In mobile card layout
- In passed sessions alert card
2. Enhanced session editing with reschedule mode:
- openMeetDialog(session, rescheduleMode = true/false)
- Tracks isRescheduling state to show appropriate UI
- Dynamic dialog title: "Reschedule Sesi" vs "Edit Sesi"
- Dynamic description based on mode
3. Enhanced saveMeetLink function:
- Detects date and time changes separately
- Updates session_date when date changed
- Recalculates duration when time changes
- Updates consulting_time_slots for new schedule
- Updates calendar event if exists
- Shows success message: "Berhasil Reschedule" with new date/time
4. Session info display improvements:
- Show current time in session info card
- Better context for rescheduling decisions
Reschedule use cases:
- Member can't make it BEFORE session → Admin clicks Reschedule, picks new slot
- Member misses session, tells admin AFTER → Admin clicks Reschedule in passed alert
- Emergency reschedule → Quick date/time change with calendar auto-update
Calendar integration:
- Existing calendar events automatically updated/moved to new time
- Time slots properly released (old) and booked (new)
UI placement:
- Passed sessions alert: First button (blue) for quick reschedule access
- Upcoming table: Between Edit and Complete buttons
- Mobile: Between Link and Complete buttons
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Problem: Passed consulting sessions stayed in "Dikonfirmasi" status indefinitely,
showing JOIN button to members even after session ended, with no admin action buttons.
Solution:
1. Admin UI (AdminConsulting.tsx):
- Add isSessionPassed() helper to check if session end time has passed
- Add "Sesi Terlewat" alert card at top with quick action buttons
- Add "Perlu Update" stat card (orange) for passed confirmed sessions
- Existing action buttons (Selesai/Batal) already work for confirmed sessions
2. Member UI (ConsultingHistory.tsx):
- Move passed confirmed sessions from "Sesi Mendatang" to new "Sesi Terlewat" section
- Remove JOIN button for passed sessions
- Show "Menunggu konfirmasi admin" status message
- Display with orange styling to indicate needs attention
3. Order Detail (OrderDetail.tsx):
- Add isConsultingSessionPassed check
- Show orange alert for passed paid sessions: "Sesi telah berakhir. Menunggu konfirmasi admin"
- Keep green alert for upcoming paid sessions
Flow:
- Session ends → Still shows "confirmed" status
- Admin sees orange alert → Clicks Selesai or Batal
- Member sees passed session → No JOIN button → Waits for admin
- Admin updates status → Session moves to completed/cancelled
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement deep linking to individual lessons with URL pattern:
- Route: /bootcamp/{product-slug}/{lessonId}
- lessonId parameter is optional for backward compatibility
- When no lessonId provided, defaults to first lesson
- Clicking lessons updates URL without page reload
- URL parameter drives lesson selection on page load
Changes:
- Update App.tsx route to accept optional :lessonId parameter
- Add lessonId extraction in Bootcamp.tsx useParams
- Implement handleSelectLesson to update URL on lesson click
- Update lesson selection logic to read from URL parameter
- Fallback to first lesson if lessonId not found
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add .trim() checks to all video source conditions
- Prevents rendering empty youtube_url as valid video
- Fixes double embed card display issue
- Update sidebar icon check to use optional chaining with trim
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add video source toggle UI (YouTube/Embed) to product edit form for bootcamps
- Remove Bootcamp menu from admin navigation (curriculum managed via Products page)
- Remove tabs from product add/edit modal (simplified to single form)
- Improve ProductCurriculum layout from 3-column (3|5|4) to 2-column (4|8)
- Modules and lessons now in left sidebar with accordion-style expansion
- Lesson editor takes 67% width instead of 33% for better content editing UX
- Add helpful tip about configuring both video sources for redundancy
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Phase 1: Rich Text Editor with Code Syntax Highlighting
- Add TipTap CodeBlock extension with lowlight for syntax highlighting
- Support multiple languages (JavaScript, TypeScript, Python, Java, C++, HTML, CSS, JSON)
- Add copy-to-clipboard button on code blocks
- Add line numbers display with CSS
- Replace textarea with RichTextEditor in CurriculumEditor
- Add DOMPurify sanitization in Bootcamp display
- Add dark theme syntax highlighting styles
Phase 2: Admin Curriculum Management Page
- Create dedicated ProductCurriculum page at /admin/products/:id/curriculum
- Three-column layout: Modules (3) | Lessons (5) | Editor (4)
- Full-page UX with drag-and-drop reordering
- Add "Manage Curriculum" button for bootcamp products in AdminProducts
- Breadcrumb navigation back to products
Phase 3: Product-Level Video Source Toggle
- Add youtube_url and embed_code columns to bootcamp_lessons table
- Add video_source and video_source_config columns to products table
- Update ProductCurriculum with separate YouTube URL and Embed Code fields
- Create smart VideoPlayer component in Bootcamp.tsx
- Support YouTube ↔ Embed switching with smart fallback
- Show "Konten tidak tersedia" warning when no video configured
- Maintain backward compatibility with existing video_url field
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Consolidate "Informasi Order" and "Detail Sesi Konsultasi" cards into one
- Remove duplicate "Order Info" card for consulting orders
- Display all information in single, cleaner merged card:
- Session details (time, date, category, notes, meet link)
- QR code for pending payments
- Expired/cancelled order handling with rebooking
- Status alerts (paid/cancelled/pending)
- Total payment
- Keep separate cards for product orders unchanged
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add "Catatan" field display in consulting order detail page
- Add dedicated "Cancelled Order" section with rebooking option
- Update status alert to show proper message for cancelled orders
- Refactor edge function to focus on calendar cleanup only
- Set payment_status to 'failed' when auto-cancelling expired orders
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Features Implemented:**
1. **Auto-Cancel Expired Consulting Orders:**
- New edge function: cancel-expired-consulting-orders
- Changes order status from 'pending' → 'cancelled'
- Cancels all associated consulting_sessions
- Deletes calendar events via delete-calendar-event
- Releases consulting_time_slots (deletes booked slots)
- Properly cleans up all resources
2. **Smart Re-Booking with Pre-filled Data:**
- OrderDetail.tsx stores expired order data in sessionStorage:
- fromExpiredOrder flag
- Original orderId
- topicCategory
- notes
- ConsultingBooking.tsx retrieves and pre-fills form on mount
- Auto-clears sessionStorage after use
3. **Improved UX for Expired Orders:**
- Clear message: "Order ini telah dibatalkan secara otomatis"
- Helpful hint: "Kategori dan catatan akan terisi otomatis"
- One-click re-booking with pre-filled data
- Member only needs to select new time slot
**How It Works:**
Flow:
1. QRIS expires → Order shows expired message
2. Member clicks "Buat Booking Baru"
3. Data stored in sessionStorage (category, notes)
4. Navigates to /consulting
5. Form auto-fills with previous data
6. Member selects new time → Books new session
**Edge Function Details:**
- Finds orders where: payment_status='pending' AND qr_expires_at < NOW()
- Cancels order status
- Cancels consulting_sessions
- Deletes consulting_time_slots
- Invokes delete-calendar-event for each session
- Returns count of processed orders
**To Deploy:**
1. Deploy cancel-expired-consulting-orders edge function
2. Set up cron job to run every 5-15 minutes:
`curl -X POST https://your-domain/functions/v1/cancel-expired-consulting-orders`
**Benefits:**
✅ Orders properly cancelled when QR expires
✅ Time slots released for other users
✅ Calendar events cleaned up
✅ Easy re-booking without re-typing data
✅ Better UX for expired payment situations
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Issue:**
- 'Buat Booking Baru' button navigated to wrong URL
- Used '/consulting-booking' but correct route is '/consulting'
**Fix:**
- Changed line 457: navigate("/consulting-booking") → navigate("/consulting")
- Button now correctly navigates to consulting booking page
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Issue:**
- Runtime error: 'Calendar is not defined' on order detail page
- Line 340 and 458 used <Calendar /> instead of <CalendarIcon />
**Root Cause:**
- OrderDetail.tsx imports: `import { Calendar as CalendarIcon } from 'lucide-react'`
- But JSX used: `<Calendar className="w-5 h-5" />` instead of `<CalendarIcon />`
- This referenced an undefined 'Calendar' variable
**Fix:**
- Changed line 340: <Calendar /> → <CalendarIcon />
- Changed line 458: <Calendar /> → <CalendarIcon />
**Context:**
- CalendarIcon is the lucide-react icon component
- Calendar (without Icon suffix) doesn't exist in this file's scope
- Combined with App.tsx fix, this fully resolves the ReferenceError
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Issue:**
- Runtime error: 'Calendar is not defined' on order detail page
- Import collision between Calendar UI component and Calendar page
**Root Cause:**
- App.tsx imported: `import Calendar from './pages/Calendar'`
- ConsultingBooking.tsx imported: `import { Calendar } from '@/components/ui/calendar'`
- Bundler couldn't resolve which 'Calendar' to use
- Resulted in undefined Calendar at runtime
**Fix:**
- Renamed Calendar page import to CalendarPage in App.tsx
- Updated route to use <CalendarPage /> instead of <Calendar />
- Eliminates naming conflict
**Files Changed:**
- src/App.tsx: Lines 18, 62
This resolves the ReferenceError that prevented members from viewing order details.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add availability checking and calendar sync to admin session editing:
**New Features:**
- Admin can now select time slots using visual picker with availability checking
- Time slot picker respects confirmed sessions and excludes current session from conflict check
- Calendar events are automatically updated when session time changes
- consulting_time_slots table is updated when time changes (old slots deleted, new slots created)
**New Component:**
- src/components/admin/TimeSlotPickerModal.tsx
- Reusable modal for time slot selection
- Shows visual grid of available time slots
- Range selection for multi-slot sessions
- Availability checking against consulting_sessions
- Supports editing (excludes current session from conflicts)
**Enhanced AdminConsulting.tsx:**
- Replaced simple time inputs with TimeSlotPickerModal
- Added state: timeSlotPickerOpen, editTotalBlocks, editTotalDuration
- Added handleTimeSlotSelect callback
- Enhanced saveMeetLink to:
- Update consulting_time_slots when time changes
- Call update-calendar-event edge function
- Update calendar event time via Google Calendar API
- Button shows selected time with duration and blocks count
**New Edge Function:**
- supabase/functions/update-calendar-event/index.ts
- Updates existing Google Calendar events when session time changes
- Uses PATCH method to update event (preserves event_id and history)
- Handles OAuth token refresh with caching
- Only updates start/end time (keeps title, description, meet link)
**Flow:**
1. Admin clicks "Edit" on session → Opens dialog
2. Admin clicks time button → Opens TimeSlotPickerModal
3. Admin selects new time → Only shows available slots
4. On save:
- consulting_sessions updated with new time
- Old consulting_time_slots deleted
- New consulting_time_slots created
- Google Calendar event updated (same event_id)
- Meet link preserved
**Benefits:**
- ✅ Prevents double-booking with availability checking
- ✅ Visual time slot selection (same UX as booking page)
- ✅ Calendar events stay in sync (no orphaned events)
- ✅ Time slots table properly maintained
- ✅ Meet link and event_id preserved during time changes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
**Issue 1: Fix end time display in booking summary**
- Now correctly shows start_time + slot_duration instead of just start_time
- Example: 09:30 → 10:00 for 1 slot (30 mins)
**Issue 2: Confirm create-google-meet-event uses consulting_sessions**
- Verified: Function already updates consulting_sessions table
- The data shown is from OLD consulting_slots table (needs migration)
**Issue 3: Delete calendar events when order is deleted**
- Enhanced delete-order function to delete calendar events before removing order
- Calls delete-calendar-event for each session with calendar_event_id
**Issue 4: Admin can now edit session time and manage calendar events**
- Added time editing inputs (start/end time) in admin dialog
- Added "Delete Link & Calendar Event" button to remove meet link
- Shows calendar event connection status (✓ Event Kalender: Terhubung)
- "Regenerate Link" button creates new meet link + calendar event
- Recalculates session duration when time changes
**Issue 5: Enhanced calendar event description**
- Now includes: Kategori, Client email, Catatan, Session ID
- Format: "Kategori: {topic}\n\nClient: {email}\n\nCatatan: {notes}\n\nSession ID: {id}"
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Migrate consulting_slots to consulting_sessions structure
- Add calendar_event_id to track Google Calendar events
- Create delete-calendar-event edge function for auto-cleanup
- Add "Tambah ke Kalender" button for members (OrderDetail, ConsultingHistory)
- Update create-google-meet-event to store calendar event ID
- Update handle-order-paid to use consulting_sessions table
- Remove deprecated create-meet-link function
- Add comprehensive documentation (CALENDAR_INTEGRATION.md, MIGRATION_GUIDE.md)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed getStatusColor to getPaymentStatusColor to match the imported helper function from statusHelpers.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changed getConsultingSlotStatusLabel to return 'Pending' instead of 'Menunggu Pembayaran' for pending_payment status, making it consistent with payment status labels and more suitable for badge display.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Update all admin pages to use tab button filters instead of dropdown selects, following the pattern from MemberAccess.tsx:
Changes:
- AdminProducts: Tab buttons for product type (only bootcamp/webinar shown) and status (active/inactive)
- AdminConsulting: Added status filter with tab buttons (pending payment, confirmed, completed, cancelled)
- AdminOrders: Tab buttons for status filter (all, paid, pending, refunded)
- AdminMembers: Tab buttons for role filter (all, admin, member)
- AdminReviews: Tab buttons for both type (all, consulting, bootcamp, webinar, general) and status (all, pending, approved)
Features added:
- Clear button (X) on search input when text is present
- Reset button appears when any filter is active
- Consistent styling with shadow-sm for active tabs and border-2 for outline tabs
- All filters in vertical stack layout for better mobile responsiveness
- Active state visually distinct with default variant and shadow
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implement comprehensive search and filter functionality across all admin dashboard pages:
- AdminProducts: Search by title/description, filter by type/status
- AdminBootcamp: Search by bootcamp title
- AdminConsulting: Search by client name, email, category, or order ID
- AdminOrders: Search by order ID/email, filter by payment status
- AdminMembers: Search by name/email, filter by role (admin/member)
- AdminReviews: Enhanced with search by title, body, reviewer, product; existing filters maintained
Features:
- Consistent UI pattern with search icon and border styling
- Result count display showing filtered vs total items
- Contextual empty state messages
- Responsive grid layout for filters
- Real-time filtering without page reload
Also fix admin page reload redirect race condition where pages would redirect to /dashboard instead of staying on current page after reload. The loading state now properly waits for admin role check to complete.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add search by client name, email, category, or order ID
- Show result count for filtered data
- Integrate with existing upcoming/past tabs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add search and filter (type, status) to AdminProducts
- Add search to AdminBootcamp
- Change mobile admin nav "Pesanan" to "Order"
- Show result counts for filtered data
- Handle empty states with helpful messages
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Wait for admin role check to complete before setting loading to false.
This prevents race condition where:
1. authLoading becomes false after session loads
2. isAdmin is still false (async check in progress)
3. Page redirects to /dashboard before isAdmin is set to true
Now loading stays true until BOTH session and admin role are loaded,
ensuring admin pages don't redirect on reload.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Group consulting slots by date in admin order detail modal
- Show time range from first slot start to last slot end
- Display session count badge for multi-slot orders
- Fix page reload redirecting to main page by ensuring loading state
is properly synchronized with Supabase session initialization
- Add mounted flag to prevent state updates after unmount
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Add pending slot state to distinguish between selected and confirmed slots
- First click: slot shows as pending (amber) with "Pilih" label
- Second click (same slot): confirms single slot selection
- Second click (different slot): creates range from pending to clicked slot
- Fix "Body already consumed" error in OAuth token refresh
- Enhance admin consulting slot display with category and notes
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes:
1. AdminConsulting.tsx: Update createMeetLink to call Supabase edge function
- Remove dependency on n8n webhook URL
- Call create-google-meet-event edge function directly
- Use environment variables for Supabase URL and anon key
- Improve error handling and user feedback
2. handle-order-paid: Add comprehensive error logging
- Log meet response status
- Log full response data
- Log errors when meet_link update fails
- Log error response text when request fails
- Better debugging for troubleshooting meet creation issues
This fixes:
- CORS issues when calling n8n webhook
- 404 errors from deleted /webhook-test/create-link endpoint
- Manual meet link creation now uses same flow as automatic
- Better visibility into meet creation failures via logs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Changes:
- Sort consulting slots by start_time before processing
- Ensures correct first/last slot selection for calendar events
- Add debug logging to track time slot calculations
- Fixes end time calculation for multi-slot consulting orders
This ensures that when multiple slots are booked:
- Slots are processed in chronological order
- Calendar event uses first slot's start and last slot's end
- Event duration correctly covers all booked slots
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Calendar Timezone Fix:
- Add +07:00 timezone offset to date strings in create-google-meet-event
- Fixes 13:00 appearing as 20:00 in Google Calendar
- Now treats times as Asia/Jakarta time explicitly
Single Calendar Event per Order:
- handle-order-paid now creates ONE event for all slots in an order
- Uses first slot's start time and last slot's end time
- Updates all slots with the same meet_link
- Prevents duplicate calendar events for multi-slot orders
Admin Consulting Page Improvements:
- Group consulting slots by order_id
- Display as single row with continuous time range (start-end)
- Show session count when multiple slots (e.g., "2 sesi")
- Consistent with member-facing ConsultingHistory component
- Updated both desktop table and mobile card layouts
- Updated both upcoming and past tabs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>