Commit Graph

73 Commits

Author SHA1 Message Date
dwindown
7cc8d47ecf Add public read access to bootcamp curriculum tables
- Enable RLS on bootcamp_modules and bootcamp_lessons tables
- Add public SELECT policies so unauthenticated users can view curriculum
- This allows kurikulum card to be displayed on bootcamp product detail pages
- Both public and authenticated roles can read the curriculum data

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-04 12:34:23 +07:00
dwindown
053465afa3 Fix email system and implement OTP confirmation flow
Email System Fixes:
- Fix email sending after payment: handle-order-paid now calls send-notification
  instead of send-email-v2 directly, properly processing template variables
- Fix order_created email timing: sent immediately after order creation,
  before payment QR code generation
- Update email templates to use short order ID (8 chars) instead of full UUID
- Add working "Akses Sekarang" buttons to payment_success and access_granted emails
- Add platform_url column to platform_settings for email links

OTP Verification Flow:
- Create dedicated /confirm-otp page for users who close registration modal
- Add link in checkout modal and email to dedicated OTP page
- Update OTP email template with better copywriting and dedicated page link
- Fix send-auth-otp to fetch platform settings for dynamic brand_name and platform_url
- Auto-login users after OTP verification in checkout flow

Admin Features:
- Add delete user functionality with cascade deletion of all related data
- Update IntegrasiTab to read/write email settings from platform_settings only
- Add test email template for email configuration testing

Cleanup:
- Remove obsolete send-consultation-reminder and send-test-email functions
- Update send-email-v2 to read email config from platform_settings
- Remove footer links (Ubah Preferensi/Unsubscribe) from email templates

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 18:02:25 +07:00
dwindown
4f9a6f4ae3 Fix variable replacement format in send-notification
The replaceVariables function was only supporting {{key}} format but
the email templates use {key} format (single braces). Updated to support
both formats for compatibility.

Changes:
- Added support for {key} format in addition to {{key}}
- Ensures all template variables are properly replaced

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 09:10:19 +07:00
dwindown
9f8ee0d7d2 Add Mailketing API support to send-notification function
The send-notification function was using SMTP by default and failing with
ConnectionRefused errors. Added Mailketing API integration to match the
email provider used by other functions (send-auth-otp, send-email-v2).

Changes:
- Added sendViaMailketing() function with form-encoded body
- Added "mailketing" case to provider switch (now the default)
- Changed default provider from "smtp" to "mailketing"
- Reads API token from mailketing_api_token or api_token settings

This ensures order_created emails are sent successfully via Mailketing
just like other email notifications in the system.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 09:06:27 +07:00
dwindown
1fbaf4d360 Fix column names in send-notification and migration
Updated to use the correct database column names:
- email_subject (not subject)
- email_body_html (not body_html)

Changes:
- send-notification/index.ts: Added fallback to check both email_subject/subject
  and email_body_html/body_html for compatibility
- Migration: Updated to use correct column names (email_subject, email_body_html)

This matches the actual database schema shown in the notification_templates table.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 08:59:49 +07:00
dwindown
485263903f Add order_created email template with QR code section
This migration updates the order_created email template to include:
- QR code image display for payment
- Order summary section with all details
- Payment link button
- QR expiry time display
- Professional layout and styling

The template uses these shortcodes:
- {qr_code_image} - Base64 QR code image generated by send-notification
- {qr_expiry_time} - QR code expiration timestamp
- {nama}, {email}, {order_id}, {tanggal_pesanan}
- {total}, {metode_pembayaran}, {produk}
- {payment_link}, {thank_you_page}, {platform_name}

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 08:53:31 +07:00
dwindown
00de020b6c Fix template lookup column name in send-notification
The function was using .eq("template_key") but the actual column name
in notification_templates table is "key". This caused "Template not found"
errors even when the template existed and was active.

Changes:
- send-notification/index.ts: Changed .eq("template_key") to .eq("key")
- Matches the pattern used in send-auth-otp and other edge functions

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 08:52:32 +07:00
dwindown
1749056542 Add order_created email with QR code generation
Enhanced email notification system to send order confirmation emails immediately after order creation with embedded QR code for payment.

Changes:
- Checkout.tsx: Added send-notification call after payment creation with comprehensive logging
- send-notification: Added QRCode library integration for generating base64 QR images for order_created emails
- NotifikasiTab.tsx: Added QR code section to default order_created template and updated shortcodes list

Technical details:
- QR code generated as base64 data URL for email client compatibility
- Fire-and-forget pattern ensures checkout flow isn't blocked
- Added detailed console logging for debugging email send issues
- New shortcodes: {qr_code_image}, {qr_expiry_time}

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-03 06:58:55 +07:00
dwindown
eee6339074 Fix email unconfirmed login flow with OTP resend and update email API field names 2026-01-02 19:33:51 +07:00
dwindown
8f46c5cfd9 Add EmailComponents and ShortcodeProcessor to shared email template renderer
- Add EmailComponents utility functions (button, alert, otpBox, etc.)
- Add ShortcodeProcessor class with DEFAULT_DATA
- Now matches src/lib/email-templates/master-template.ts exactly
- Edge functions can now use helpful components like EmailComponents.otpBox()
2026-01-02 17:20:48 +07:00
dwindown
dafa4eeeb3 Refactor: Extract master template to shared file and add unconfirmed email handling
- Create supabase/shared/email-template-renderer.ts for code reuse
- Update send-auth-otp to import from shared file (eliminates 260 lines of duplication)
- Add isResendOTP state to track existing user email confirmation
- Update login error handler to detect unconfirmed email
- Show helpful message when user tries to login with unconfirmed email

This addresses:
1. Code duplication between src/lib and edge functions
2. User experience for unconfirmed email login attempts
2026-01-02 17:16:59 +07:00
dwindown
bd3841b716 Add master template wrapper to OTP emails
- Add EmailTemplateRenderer class to send-auth-otp edge function
- Wrap OTP email content in master template with brutalist design
- Email now includes proper header, footer, and styling
- No changes needed to checkout flow (uses auth page for registration)

Benefits:
- Professional branded emails with ACCESS HUB header
- Consistent brutalist design across all emails
- Responsive layout
- Better email client compatibility
2026-01-02 15:19:41 +07:00
dwindown
08e56a22d8 Fix send-auth-otp: Remove notification_logs references
- Remove notification_logs table references (table doesn't exist)
- This was causing the function to crash after sending email
- Now the function should complete successfully
- Added better email payload logging
- Keep .env file for local development
2026-01-02 15:07:41 +07:00
dwindown
06d6845456 Fix API token mapping and add extensive debug logging
- Fixed api_token vs mailketing_api_token column mapping
- Added comprehensive debug logging to send-auth-otp
- Added fallback logic for missing settings fields
- Improved error messages for troubleshooting

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-02 14:31:23 +07:00
dwindown
c6250d2b47 Fix notification_templates table column names
Update auth email template migration and edge function to use correct column names:
- template_key → key
- subject → email_subject
- html_content → email_body_html

Matches existing notification_templates schema used in NotifikasiTab.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-02 13:41:30 +07:00
dwindown
0d29c953c1 Implement OTP-based email verification system
Add custom email verification using 6-digit OTP codes via Mailketing API:

Database:
- Create auth_otps table with 15-minute expiry
- Add indexes and RLS policies for security
- Add cleanup function for expired tokens
- Insert default auth_email_verification template

Edge Functions:
- send-auth-otp: Generate OTP, store in DB, send via Mailketing
- verify-auth-otp: Validate OTP, confirm email in Supabase Auth

Frontend:
- Add OTP input state to auth page
- Implement send/verify OTP in useAuth hook
- Add resend countdown timer (60 seconds)
- Update auth flow: signup → OTP verification → login

Features:
- Instant email delivery (no queue/cron)
- 6-digit OTP with 15-minute expiry
- Resend OTP with cooldown
- Admin-configurable email templates
- Indonesian UI text

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-02 13:27:46 +07:00
dwindown
60baf32f73 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>
2026-01-01 23:54:32 +07:00
dwindown
84de0a7efe Fix platform_settings table name and RLS policy
- 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>
2026-01-01 01:15:09 +07:00
dwindown
726250507a Fix Plyr controls and site_settings permissions
## 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>
2026-01-01 01:07:52 +07:00
dwindown
95fd4d3859 Add video chapter/timeline navigation feature
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>
2025-12-31 23:31:23 +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
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
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
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
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
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
e347a780f8 Fix review system: display real names and check approval status
Changes:
- ProductReviews.tsx: Use LEFT JOIN and fetch reviewer_name field
- ReviewModal.tsx: Store reviewer_name at submission time
- ProductDetail.tsx: Check is_approved=true in checkUserReview()
- Add migration for reviewer_name column and approval index

This fixes two issues:
1. Reviews now show real account names instead of "Anonymous"
2. Members no longer see "menunggu moderasi" after approval

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 22:29:48 +07:00
dwindown
4ccd1cb96f Add paragraph spacing to prose content styling
Add proper margin (my-4) to paragraphs in Tiptap-rendered content.
This ensures proper spacing between paragraphs when displaying
rich text content in product detail pages and other areas.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-25 13:57:53 +07:00
dwindown
eba37df4d7 Remove PayPal, simplify to QRIS-only with in-app QR display
- Remove PayPal payment option from checkout
- Add qr_string and qr_expires_at columns to orders table
- Update create-payment to store QR string in database
- Update pakasir-webhook to clear QR string after payment
- Simplify Checkout to redirect to order detail page
- Clean up unused imports and components

Flow:
1. User checks out with QRIS (only option)
2. Order created with payment_method='qris'
3. QR string stored in database
4. User redirected to Order Detail page
5. QR code displayed in-app with polling
6. After payment, QR string cleared, access granted

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-24 00:12:04 +07:00
dwindown
1a36f831cc Refactor: Rename create-pakasir-payment to create-payment
- Rename function to abstract payment provider details
- Add support for both QRIS and PayPal methods
- Update frontend to use generic create-payment function
- Remove provider-specific naming from UI/UX
- Payment provider (Pakasir) is now an implementation detail

Response format:
- QRIS: returns qr_string for in-app display, payment_url as fallback
- PayPal: returns payment_url for redirect

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 21:41:47 +07:00
dwindown
a9f7c9b07a Create Pakasir payment edge function to fix CORS issue
- Create create-pakasir-payment edge function to handle payment creation server-side
- Update ConsultingBooking.tsx to use edge function instead of direct API call
- Update Checkout.tsx to use edge function instead of direct API call
- Add config.toml entry for create-pakasir-payment function
- Removes CORS errors when calling Pakasir API from frontend

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 21:20:40 +07:00
dwindown
94403bd634 Add order deletion functionality
- Add delete button to AdminOrders dialog with Trash2 and AlertTriangle icons
- Create delete-order edge function to handle deletion requests
- Add database migration for delete_order function with comprehensive cleanup
- Update config.toml to register delete-order edge function
- Deletion sequence: reviews → consulting slots → order items → user access → order

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 18:06:40 +07:00
dwindown
01579ac299 Refactor payment flow to use database triggers (Clean Architecture)
BREAKING CHANGE: Complete refactor of payment handling

New Architecture:
1. pakasir-webhook (120 lines -> was 535 lines)
   - Only verifies signature and updates order status
   - Removed: SMTP, email templates, notification logic

2. Database Trigger (NEW)
   - Automatically fires when payment_status = 'paid'
   - Calls handle-order-paid edge function
   - Works for webhook AND manual admin updates

3. handle-order-paid (NEW edge function)
   - Grants user access for products
   - Creates Google Meet events for consulting
   - Sends notifications via send-email-v2
   - Triggers webhooks

Benefits:
- Single Responsibility: Each function has one clear purpose
- Trigger works for both webhook and manual admin actions
- Easier to debug and maintain
- Reusable notification system

Migration required: Run 20241223_payment_trigger.sql

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:59:13 +07:00
dwindown
9d7d76b04d Add consulting slots display with Join Meet button
- Member OrderDetail page: Shows consulting slots with date/time and Join Meet button
- Admin Orders dialog: Shows consulting slots with meet link access
- Meet button only visible when payment_status is 'paid'
- Both pages show slot status (confirmed/pending)

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:45:48 +07:00
dwindown
ce531c8d46 Add Google Meet event creation to payment webhook
When order is paid, automatically create Google Meet events for all consulting slots.
The meet_link is saved to consulting_slots table and included in notifications.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:41:22 +07:00
dwindown
7bf13b88d2 Add detailed debug info to edge function response
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:27:33 +07:00
dwindown
3f8c2b7c01 Fix body consumption: use req.text() instead of req.json()
Using req.text() first then parsing JSON gives us more control and avoids
stream consumption issues with Deno/Supabase edge functions.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:14:56 +07:00
dwindown
8e476a7a82 Fix body consumed error: disable JWT verification for create-google-meet-event
The JWT verification was consuming the request body before our handler could read it.
Since this function is called from authenticated sessions, we verify differently.

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-23 16:02:23 +07:00
dwindown
8f167c85a8 Handle 'Body already consumed' from React Strict Mode duplicate calls
- Catch TypeError when req.json() is called on consumed stream
- Return success response for duplicate calls (first call handles the actual work)
- This handles React Strict Mode firing onClick twice in parallel
- Only the first call that successfully reads body will process the request
2025-12-23 15:07:11 +07:00
dwindown
d358d95486 Clean up unused pendingRequests variable 2025-12-23 14:55:52 +07:00
dwindown
cc66e96f61 Fix 'Body already consumed' error by using req.clone()
- Use req.clone().json() to handle multiple reads from same request
- This happens when React Strict Mode fires requests twice
- Cloning the request allows reading body multiple times safely
2025-12-23 14:52:40 +07:00