Commit Graph

203 Commits

Author SHA1 Message Date
dwindown
ad7b6130b1 Fix AlertCircle import error in AdminConsulting
Added missing AlertCircle import from lucide-react to fix blank admin consulting page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-31 14:09:51 +07:00
dwindown
f68c8ee1c4 Add reschedule functionality for consulting sessions
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>
2025-12-31 13:19:45 +07:00
dwindown
0be27ccf99 Handle passed consulting sessions for admin and member
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>
2025-12-31 12:59:07 +07:00
dwindown
9e76d07cc2 Add routeable lesson URLs for bootcamp pages
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>
2025-12-31 12:47:42 +07:00
dwindown
a9ad84eb23 Fix duplicate video embed when youtube_url is empty string
- 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>
2025-12-30 21:11:35 +07:00
dwindown
94aca1edec Add product-level video source toggle and improve curriculum UX
- 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>
2025-12-30 21:04:40 +07:00
dwindown
da71acb431 Enhance bootcamp with rich text editor, curriculum management, and video toggle
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>
2025-12-30 17:07:31 +07:00
dwindown
52ec0b9b86 Merge consulting order details into single card
- 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>
2025-12-28 22:01:58 +07:00
dwindown
ac88e17856 Improve cancelled order display and add notes to order detail
- 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>
2025-12-28 21:36:01 +07:00
dwindown
3eb53406c9 Auto-cancel expired consulting orders and prefill re-booking
**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>
2025-12-28 18:13:20 +07:00
dwindown
b88e308b84 Fix consulting booking navigation URL
**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>
2025-12-28 18:01:53 +07:00
dwindown
5c20ea16a3 Fix incorrect Calendar usage in OrderDetail.tsx
**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>
2025-12-28 17:55:33 +07:00
dwindown
5a53cf3f99 Fix Calendar naming conflict in App.tsx
**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>
2025-12-28 16:34:27 +07:00
dwindown
9bb922f5aa Integrate TimeSlotPickerModal and calendar event updates
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>
2025-12-28 16:02:00 +07:00
dwindown
b1bd092eb8 Fix booking summary end time and enhance calendar event management
**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>
2025-12-28 14:30:39 +07:00
dwindown
5ab4e6b974 Add calendar event lifecycle management and "Add to Calendar" feature
- 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>
2025-12-28 13:54:16 +07:00
dwindown
952bb209cf Fix mobile bottom navigation wording to match desktop sidebar
Updated mobile navigation labels to be consistent with desktop sidebar:
- User nav: "Home" → "Dashboard", "Kelas" → "Akses", "Pesanan" → "Order"
- Admin nav: "Pengguna" → "Member" (matches sidebar exactly)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-28 01:14:29 +07:00
dwindown
a8341a42ee Fix badge color function name in Dashboard
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>
2025-12-28 01:00:02 +07:00
dwindown
2f198a4d72 Fix consulting slot status label to use 'Pending'
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>
2025-12-28 00:55:08 +07:00
dwindown
0a299466d8 Replace dropdown filters with tab buttons across admin pages
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>
2025-12-28 00:33:43 +07:00
dwindown
c993abe1e9 Add search and filter features to admin pages
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>
2025-12-28 00:17:53 +07:00
dwindown
690268362a feat: add search to AdminConsulting page
- 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>
2025-12-28 00:08:21 +07:00
dwindown
3e418759a1 feat: add search and filter to admin pages
- 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>
2025-12-28 00:07:07 +07:00
dwindown
0e3a45cfe2 fix: prevent admin page reload redirect to dashboard
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>
2025-12-27 23:55:46 +07:00
dwindown
79e1bd82fc fix: consulting slots display and auth reload redirect
- 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>
2025-12-27 23:47:53 +07:00
dwindown
777d989d34 feat: improve consulting booking UX - allow single slot selection
- 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>
2025-12-27 23:40:54 +07:00
dwindown
4d8f66ed3a Fix meet link creation to use edge function instead of n8n webhook
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>
2025-12-27 08:46:55 +07:00
dwindown
47d78cbd98 Fix consulting slots ordering and add debug logging
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>
2025-12-27 08:27:34 +07:00
dwindown
42d6bd98e2 Fix calendar timezone and group consulting slots by order
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>
2025-12-27 01:34:40 +07:00
dwindown
3f0acca658 Fix admin consulting page query
Change from relationship query to manual join to avoid foreign key issues with profiles table

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 01:22:15 +07:00
dwindown
17440cdf89 Fix consulting order processing and display
- Fix consulting history to show continuous time range (09:00 - 11:00) instead of listing individual slots
- Add foreign key relationships for consulting_slots (order_id and user_id)
- Fix handle-order-paid to query profiles(email, name) instead of full_name
- Add completed consulting sessions with recordings to Member Access page
- Add user_id foreign key constraint to consulting_slots table
- Add orders foreign key constraint for consulting_slots relationship

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-27 01:17:47 +07:00
dwindown
73c03285ea Debug: Add extensive logging to handle-order-paid
Added detailed logging to diagnose why consulting slots aren't being updated:
- Log order details including consulting_slots count
- Log whether order is detected as consulting order
- Log slot update result and any errors

This will help identify where the process is failing.
2025-12-27 00:07:00 +07:00
dwindown
293d5bd65d Fix: Call handle-order-paid directly from webhook instead of relying on DB trigger
The database trigger approach wasn't working because the trigger either doesn't
exist or the required database settings (app.base_url, app.service_role_key) aren't
configured.

This is a simpler, more reliable solution:
- pakasir-webhook updates order to 'paid' status
- pakasir-webhook then directly calls handle-order-paid edge function
- No dependency on database triggers or settings

This matches how other parts of the system work - direct function calls with
environment variables, not database triggers.
2025-12-26 23:54:13 +07:00
dwindown
390fde9bf2 Fix: Handle consulting orders properly in handle-order-paid edge function
Critical bug fix: Consulting orders were not being processed after payment because
the function checked order_items for consulting products, but consulting orders
don't have order_items - they have consulting_slots instead.

Changes:
- Fetch consulting_slots along with order_items in the query
- Check for consulting_slots.length > 0 to detect consulting orders
- Update consulting_slots status from 'pending_payment' to 'confirmed'
- Create Google Meet events for each consulting slot
- Send consulting_scheduled notification

This fixes the issue where:
- Consulting slots stayed in 'pending_payment' status after payment
- No meet links were generated
- No access was granted
- Schedules didn't show up in admin or member dashboard
2025-12-26 23:25:55 +07:00
dwindown
1743f95000 Fix: Add missing payment_method to consulting order creation
The consulting booking flow was missing payment_method: 'qris' when creating
orders. This caused the OrderDetail page to skip rendering the QR code section
because it checks order.payment_method === 'qris'.

Product orders already had this field, which is why QR codes displayed correctly
for them but not for consulting orders.

The root cause was in ConsultingBooking.tsx line 303-314 where the order insert
was missing the payment_method field that was present in Checkout.tsx.
2025-12-26 22:49:09 +07:00
dwindown
a567b683af Fix consulting order QR display and remove duplicate slots card
1. QR Code Display Fix:
   - Removed qr_string from required condition
   - Added fallback for when QR is still processing
   - Shows payment_url button if QR string not available yet
   - Helps users pay while QR code is being generated

2. Consulting Slots Display Fix:
   - Removed duplicate "Detail Jadwal" card
   - Slots now only shown once in main session card
   - Displays as continuous time range (start to end)
   - Shows total duration (e.g., "3 blok (135 menit)")

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 22:24:21 +07:00
dwindown
2dae2fdc33 Fix consulting order detail to show slots and QR code
Fixed isConsultingOrder detection:
- Now checks consultingSlots.length > 0 instead of only checking order_items
- Consulting orders don't have order_items, only consulting_slots

Always fetch consulting slots:
- Removed conditional check that only fetched slots for consulting products
- Now always queries consulting_slots table for any order
- This ensures consulting booking info displays correctly

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 21:32:13 +07:00
dwindown
c09d8b0c2a Fix consulting booking flow and export CSV format
1. CSV Export: Use raw numbers for Total and Refund Amount columns
   - Changed from formatted IDR (with dots) to plain numbers
   - Prevents Excel from breaking values with thousand separators

2. Consulting Booking Flow:
   - Fixed "Booking Sekarang" to navigate to order detail instead of redirecting to Pakasir
   - Payment QR code now displays in OrderDetail page
   - Consulting orders show slot details instead of empty items list

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 21:09:06 +07:00
dwindown
bf212fb973 Add CSV export functionality for admin orders
- Created exportCSV utility with convertToCSV, downloadCSV, formatExportDate, formatExportIDR
- Added export button to AdminOrders page with loading state
- Export includes all order fields: ID, email, total, status, payment method, date, refund info
- CSV format compatible with Excel and Google Sheets

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 18:25:43 +07:00
dwindown
5a05203f2b Update all pages to use centralized status helpers
Changes:
- Update MemberOrders to use getPaymentStatusLabel and getPaymentStatusColor
- Update OrderDetail to use centralized helpers
- Remove duplicate getStatusColor and getStatusLabel functions
- Dashboard.tsx already using imported helpers

Benefits:
- DRY principle - single source of truth
- Consistent Indonesian labels everywhere
- Easy to update status styling in one place

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 18:19:23 +07:00
dwindown
d089fcc769 Create centralized status management system
- Add statusHelpers.ts with single source of truth for all status labels/colors
- Update AdminOrders to use centralized helpers
- Add utility functions: canRefundOrder, canCancelOrder, canMarkAsPaid
- Improve consistency across payment status handling

Benefits:
- Consistent Indonesian labels everywhere
- DRY principle - no more duplicate switch statements
- Easy to update status styling in one place
- Reusable across all components

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 17:07:56 +07:00
dwindown
81bbafcff0 Add refund system and meet link management
Refund System:
- Add refund processing with amount and reason tracking
- Auto-revoke product access on refund
- Support full and partial refunds
- Add database fields for refund tracking

Meet Link Management:
- Show meet link status badge (Ready/Not Ready)
- Add manual meet link creation/update form
- Allow admin to create meet links if auto-creation fails

Database Migration:
- Add refund_amount, refund_reason, refunded_at, refunded_by to orders
- Add cancellation_reason to orders and consulting_slots

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 17:05:25 +07:00
dwindown
b955445dea Add filters to Member Access and Member Orders pages
- Member Access: Add product kind filter pills + search input
- Member Orders: Add order status filter pills with counts
- Both pages show results count and empty state when no results
- Include reset filter button and clear search functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 16:10:26 +07:00
dwindown
a824e101ed Use live profile data for reviews instead of frozen reviewer_name
Changes:
- Revert to using profiles!user_id (name, avatar_url) JOIN for reviews
- Remove reviewer_name storage from ReviewModal (no longer needed)
- Add avatar display to ReviewCard component
- Reviews now sync automatically with profile changes
- Public queries safely expose only name + avatar via RLS

This ensures:
- Name/avatar changes update across all reviews automatically
- No frozen/outdated reviewer data
- Only public profile fields exposed (secure)
- Reviews serve as live, credible social proof

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 15:39:58 +07:00
dwindown
74b7dd09ea Fix reviewer name priority to use live profile data first
Changed fallback order from:
  reviewer_name || profiles.name || 'Anonymous'
To:
  profiles.name || reviewer_name || 'Anonymous'

This ensures:
1. Live profile name is always shown (current data)
2. Falls back to stored reviewer_name if profile deleted
3. Shows "Anonymous" as last resort

Fixes issue where name changes don't reflect on old reviews

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 09:36:54 +07:00
dwindown
9b2ac9beee Hide Quick Access section when no scheduled events available
- Check if quickAccessItems has any items before rendering section
- If no consulting/webinar events qualify, entire section is hidden
- Prevents empty "Akses Cepat" section from showing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 01:53:30 +07:00
dwindown
734aa967ac Refactor quick access section to only show scheduled events
Changes:
- Add consulting slots fetching to get confirmed upcoming sessions
- Update getQuickAction logic:
  * Consulting: Only show if has confirmed upcoming slot with meet_link
  * Webinar: Only show if event_start + duration hasn't ended
  * Bootcamp: Removed from quick access (self-paced, not scheduled)
- Filter out items without valid quick actions
- Remove unused Calendar and BookOpen imports

Quick access now truly means "it is scheduled, here's the shortcut to join"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 01:40:52 +07:00
dwindown
91bec42c4b Add missing Badge import to Bootcamp page
Fixes "Badge is not defined" error in bootcamp focus page.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 01:14:43 +07:00
dwindown
e512956444 Add celebratory review UI to Bootcamp page & fix access page
Bootcamp page changes:
- Add UserReview interface to store full review data
- Fetch review data with is_approved status
- Add celebratory UI when review is approved:
  - Gradient background with brand accent
  - "Ulasan Anda Terbit!" heading with "Disetujui" badge
  - Display user's review with stars, title, body
  - Publication date
- Show pending state with clock icon while waiting approval
- Update onSuccess callback to refresh review data

MemberAccess page changes:
- Change "Lanjutkan Bootcamp" to "Mulai Bootcamp" (clearer)
- Fix webinar action buttons:
  - Check if event_start has passed
  - Only show "Gabung Webinar" if webinar hasn't ended
  - Show "Tonton Rekaman" button if recording_url exists
  - Show "Rekaman segera tersedia" badge for passed webinars without recording

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 01:11:11 +07:00
dwindown
f1fb2758f8 Fix webinar join logic to use event_start + duration
Users can now join webinar even if it's already started:
- isWebinarJoinable(): returns true if current time <= event_start + duration
- isWebinarEnded(): returns true if current time > event_start + duration
- "Gabung Webinar" button shows as long as webinar hasn't ended
- This allows latecomers to join immediately

Previously used only event_start which prevented joining after start time.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-26 00:56:03 +07:00