diff --git a/src/pages/ConsultingBooking.tsx b/src/pages/ConsultingBooking.tsx
index 4929ac4..6f6d510 100644
--- a/src/pages/ConsultingBooking.tsx
+++ b/src/pages/ConsultingBooking.tsx
@@ -82,6 +82,31 @@ export default function ConsultingBooking() {
useEffect(() => {
fetchData();
+
+ // Check for pre-filled data from expired order
+ const expiredOrderData = sessionStorage.getItem('expiredConsultingOrder');
+ if (expiredOrderData) {
+ try {
+ const data = JSON.parse(expiredOrderData);
+ if (data.fromExpiredOrder) {
+ // Prefill form with expired order data
+ if (data.topicCategory) setSelectedCategory(data.topicCategory);
+ if (data.notes) setNotes(data.notes);
+
+ // Show notification to user
+ setTimeout(() => {
+ // You could add a toast notification here if you have toast set up
+ console.log('Pre-filled data from expired order:', data);
+ }, 100);
+
+ // Clear the stored data after using it
+ sessionStorage.removeItem('expiredConsultingOrder');
+ }
+ } catch (err) {
+ console.error('Error parsing expired order data:', err);
+ sessionStorage.removeItem('expiredConsultingOrder');
+ }
+ }
}, []);
useEffect(() => {
diff --git a/src/pages/member/OrderDetail.tsx b/src/pages/member/OrderDetail.tsx
index 71cd94b..91dea74 100644
--- a/src/pages/member/OrderDetail.tsx
+++ b/src/pages/member/OrderDetail.tsx
@@ -449,15 +449,32 @@ export default function OrderDetail() {
{isConsultingOrder ? (
- // Consulting order - show booking button
+ // Consulting order - show booking button with pre-filled data
Order ini telah dibatalkan secara otomatis karena waktu pembayaran habis.
-
) : (
// Product order - show regenerate button
diff --git a/supabase/functions/cancel-expired-consulting-orders/index.ts b/supabase/functions/cancel-expired-consulting-orders/index.ts
new file mode 100644
index 0000000..eba264f
--- /dev/null
+++ b/supabase/functions/cancel-expired-consulting-orders/index.ts
@@ -0,0 +1,136 @@
+import { serve } from "https://deno.land/std@0.190.0/http/server.ts";
+import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
+
+const corsHeaders = {
+ "Access-Control-Allow-Origin": "*",
+ "Access-Control-Allow-Headers": "authorization, x-client-info, apikey, content-type",
+};
+
+serve(async (req: Request): Promise => {
+ if (req.method === "OPTIONS") {
+ return new Response(null, { headers: corsHeaders });
+ }
+
+ try {
+ const supabaseUrl = Deno.env.get("SUPABASE_URL")!;
+ const supabaseServiceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!;
+ const supabase = createClient(supabaseUrl, supabaseServiceKey);
+
+ console.log("[CANCEL-EXPIRED] Starting check for expired consulting orders");
+
+ // Find expired pending consulting orders
+ const now = new Date().toISOString();
+
+ // Get orders with consulting_sessions that are pending payment and QR is expired
+ const { data: expiredOrders, error: queryError } = await supabase
+ .from("orders")
+ .select(`
+ id,
+ payment_status,
+ qr_expires_at,
+ consulting_sessions (
+ id,
+ topic_category,
+ notes,
+ session_date,
+ start_time,
+ end_time
+ )
+ `)
+ .eq("payment_status", "pending")
+ .lt("qr_expires_at", now)
+ .not("consulting_sessions", "is", null);
+
+ if (queryError) {
+ console.error("[CANCEL-EXPIRED] Query error:", queryError);
+ throw queryError;
+ }
+
+ if (!expiredOrders || expiredOrders.length === 0) {
+ console.log("[CANCEL-EXPIRED] No expired orders found");
+ return new Response(
+ JSON.stringify({
+ success: true,
+ message: "No expired orders to process",
+ processed: 0
+ }),
+ { headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+ }
+
+ console.log(`[CANCEL-EXPIRED] Found ${expiredOrders.length} expired orders`);
+
+ let processedCount = 0;
+
+ // Process each expired order
+ for (const order of expiredOrders) {
+ console.log(`[CANCEL-EXPIRED] Processing order: ${order.id}`);
+
+ // Update order status to cancelled
+ const { error: updateError } = await supabase
+ .from("orders")
+ .update({ status: "cancelled" })
+ .eq("id", order.id);
+
+ if (updateError) {
+ console.error(`[CANCEL-EXPIRED] Failed to update order ${order.id}:`, updateError);
+ continue;
+ }
+
+ // Cancel all consulting sessions for this order
+ if (order.consulting_sessions && order.consulting_sessions.length > 0) {
+ for (const session of order.consulting_sessions) {
+ // Delete calendar event if exists
+ if (session.calendar_event_id) {
+ try {
+ await supabase.functions.invoke('delete-calendar-event', {
+ body: { session_id: session.id }
+ });
+ console.log(`[CANCEL-EXPIRED] Deleted calendar event for session: ${session.id}`);
+ } catch (err) {
+ console.log(`[CANCEL-EXPIRED] Failed to delete calendar event: ${err}`);
+ // Continue anyway
+ }
+ }
+
+ // Update session status to cancelled
+ await supabase
+ .from("consulting_sessions")
+ .update({ status: "cancelled" })
+ .eq("id", session.id);
+
+ // Delete or release time slots
+ await supabase
+ .from("consulting_time_slots")
+ .delete()
+ .eq("session_id", session.id);
+
+ console.log(`[CANCEL-EXPIRED] Cancelled session: ${session.id}`);
+ }
+ }
+
+ processedCount++;
+ }
+
+ console.log(`[CANCEL-EXPIRED] Successfully processed ${processedCount} orders`);
+
+ return new Response(
+ JSON.stringify({
+ success: true,
+ message: `Successfully cancelled ${processedCount} expired consulting orders`,
+ processed: processedCount
+ }),
+ { headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+
+ } catch (error: any) {
+ console.error("[CANCEL-EXPIRED] Error:", error);
+ return new Response(
+ JSON.stringify({
+ success: false,
+ error: error.message || "Internal server error"
+ }),
+ { status: 500, headers: { ...corsHeaders, "Content-Type": "application/json" } }
+ );
+ }
+});