import { useEffect, useState, useCallback } from "react"; import { useNavigate, useParams, Link } from "react-router-dom"; import { AppLayout } from "@/components/AppLayout"; import { useAuth } from "@/hooks/useAuth"; import { supabase } from "@/integrations/supabase/client"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Separator } from "@/components/ui/separator"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { formatIDR, formatDate } from "@/lib/format"; import { ArrowLeft, Package, CreditCard, Calendar, AlertCircle, Video, Clock, RefreshCw } from "lucide-react"; import { QRCodeSVG } from "qrcode.react"; import { getPaymentStatusLabel, getPaymentStatusColor, getProductTypeLabel } from "@/lib/statusHelpers"; interface OrderItem { id: string; product_id: string; quantity: number; products: { title: string; type: string; slug: string; price: number; sale_price: number | null; }; } interface Order { id: string; total_amount: number; status: string; payment_status: string | null; payment_method: string | null; payment_provider: string | null; payment_url: string | null; qr_string: string | null; qr_expires_at: string | null; created_at: string; updated_at: string; order_items: OrderItem[]; } interface ConsultingSlot { id: string; date: string; start_time: string; end_time: string; status: string; meet_link?: string; } export default function OrderDetail() { const { id } = useParams<{ id: string }>(); const { user, loading: authLoading } = useAuth(); const navigate = useNavigate(); const [order, setOrder] = useState(null); const [consultingSlots, setConsultingSlots] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [timeRemaining, setTimeRemaining] = useState(""); const [isPolling, setIsPolling] = useState(false); const [regeneratingQR, setRegeneratingQR] = useState(false); // Check if QR is expired const isQrExpired = order?.qr_expires_at ? new Date(order.qr_expires_at) < new Date() : false; // Check if this is a consulting order const isConsultingOrder = order?.order_items?.some( (item: OrderItem) => item.products.type === "consulting" ) || false; // Memoized fetchOrder to avoid recreating on every render const fetchOrder = useCallback(async () => { if (!user || !id) return; try { const { data, error: queryError } = await supabase .from("orders") .select(` *, order_items ( id, product_id, quantity, products (title, type, slug, price, sale_price) ) `) .eq("id", id) .eq("user_id", user.id) .single(); if (queryError) { console.error("Order fetch error:", queryError); return null; } return data as Order; } catch (err) { console.error("Unexpected error:", err); return null; } }, [user, id]); useEffect(() => { if (authLoading) return; if (!user) { navigate("/auth"); return; } if (id) { const loadOrder = async () => { setLoading(true); const data = await fetchOrder(); if (!data) { setError("Order tidak ditemukan"); } else { setOrder(data); // Fetch consulting slots if this is a consulting order const hasConsultingProduct = data.order_items.some( (item: OrderItem) => item.products.type === "consulting" ); if (hasConsultingProduct) { const { data: slots } = await supabase .from("consulting_slots") .select("*") .eq("order_id", id) .order("date", { ascending: true }); if (slots) { setConsultingSlots(slots as ConsultingSlot[]); } } } setLoading(false); }; loadOrder(); } }, [user, authLoading, id, fetchOrder]); // Poll for payment status if order is pending and has QR string useEffect(() => { if (!order || order.payment_status === "paid") return; // Only poll if there's a QR string or it's a pending QRIS payment const shouldPoll = order.qr_string || (order.payment_status === "pending" && order.payment_method === "qris"); if (!shouldPoll) return; setIsPolling(true); const interval = setInterval(async () => { const updatedOrder = await fetchOrder(); if (updatedOrder) { setOrder(updatedOrder); // Stop polling if paid if (updatedOrder.payment_status === "paid") { clearInterval(interval); setIsPolling(false); } } }, 10000); // Poll every 10 seconds return () => { clearInterval(interval); setIsPolling(false); }; }, [order, fetchOrder]); // Countdown timer for QR expiration useEffect(() => { if (!order?.qr_expires_at) return; const updateCountdown = () => { const now = new Date().getTime(); const expiresAt = new Date(order.qr_expires_at!).getTime(); const distance = expiresAt - now; if (distance < 0) { setTimeRemaining("QR Code kadaluarsa"); return; } const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((distance % (1000 * 60)) / 1000); setTimeRemaining(`${minutes}m ${seconds}s`); }; updateCountdown(); const timer = setInterval(updateCountdown, 1000); return () => clearInterval(timer); }, [order?.qr_expires_at]); const getTypeLabel = (type: string) => { switch (type) { case "consulting": return "Konsultasi"; case "webinar": return "Webinar"; case "bootcamp": return "Bootcamp"; default: return type; } }; // Handle QR regeneration for expired product orders const handleRegenerateQR = async () => { if (!order || isConsultingOrder) return; setRegeneratingQR(true); try { // Call create-payment function with existing order_id const { data, error } = await supabase.functions.invoke('create-payment', { body: { order_id: order.id, amount: order.total_amount, description: order.order_items.map((item: OrderItem) => item.products.title).join(", "), }, }); if (error) { throw error; } // Refresh order data to get new QR const updatedOrder = await fetchOrder(); if (updatedOrder) { setOrder(updatedOrder); } // Restart polling setIsPolling(true); } catch (error) { console.error('QR regeneration error:', error); setError('Gagal me-regenerate QR code. Silakan coba lagi atau buat order baru.'); } finally { setRegeneratingQR(false); } }; if (authLoading || loading) { return (
); } if (error) { return (

Error

{error}

); } if (!order) return null; return (

Detail Order

#{order.id.slice(0, 8)}

{getPaymentStatusLabel(order.payment_status || order.status)}
{/* Order Info */} Informasi Order

Tanggal Order

{formatDate(order.created_at)}

Terakhir Update

{formatDate(order.updated_at)}

{order.payment_method && (

Metode Pembayaran

{order.payment_method}

)} {order.payment_provider && (

Provider

{order.payment_provider}

)}
{/* QR Code Display for pending QRIS payments */} {order.payment_status === "pending" && order.payment_method === "qris" && order.qr_string && !isQrExpired && (
Scan QR code ini dengan aplikasi e-wallet atau mobile banking Anda {timeRemaining && ( (Kadaluarsa dalam {timeRemaining}) )}

{formatIDR(order.total_amount)}

Order ID: {order.id.slice(0, 8)}

{isPolling && (
Menunggu pembayaran...
)}
🔒 Pembayaran Aman ⚡ QRIS Standar Bank Indonesia
{order.payment_url && ( )}
)} {/* Expired QR Handling */} {order.payment_status === "pending" && order.payment_method === "qris" && isQrExpired && (
{isConsultingOrder ? "Waktu pembayaran telah habis. Slot konsultasi telah dilepaskan. Silakan buat booking baru." : "QR Code telah kadaluarsa. Anda dapat me-regenerate QR code untuk melanjutkan pembayaran."} {isConsultingOrder ? ( // Consulting order - show booking button

Order ini telah dibatalkan secara otomatis karena waktu pembayaran habis.

) : ( // Product order - show regenerate button

{formatIDR(order.total_amount)}

Order ID: {order.id.slice(0, 8)}

)}
)} {/* Fallback button for pending payments without QR */} {order.payment_status === "pending" && !order.qr_string && order.payment_url && ( )}
{/* Smart Item/Service Display */} {order.order_items.length > 0 ? ( // === Product Orders === Item Pesanan
{order.order_items.map((item) => (
{item.products.title}
{getTypeLabel(item.products.type)} x{item.quantity}

{formatIDR(item.products.sale_price || item.products.price)}

))}
Total {formatIDR(order.total_amount)}
) : consultingSlots.length > 0 ? ( // === Consulting Orders === {/* Summary Card */}

Waktu Konsultasi

{consultingSlots[0].start_time.substring(0,5)} - {consultingSlots[consultingSlots.length-1].end_time.substring(0,5)}

{consultingSlots.length} blok ({consultingSlots.length * 45} menit)

{consultingSlots[0]?.meet_link && ( )}
{/* Status Alert */} {order.payment_status === "paid" ? ( ) : ( Selesaikan pembayaran untuk mengkonfirmasi jadwal sesi konsultasi. )}
) : null} {/* Consulting Slots Detail */} {consultingSlots.length > 0 && (
{consultingSlots.map((slot) => (
{slot.status === "confirmed" ? "Terkonfirmasi" : slot.status}

{new Date(slot.date).toLocaleDateString("id-ID", { weekday: "long", year: "numeric", month: "long", day: "numeric" })}

{slot.start_time.substring(0, 5)} - {slot.end_time.substring(0, 5)} WIB

{slot.meet_link && order.payment_status === "paid" && ( )} {slot.meet_link && order.payment_status !== "paid" && (

Link tersedia setelah pembayaran

)} {!slot.meet_link && (

Link akan dikirim via email

)}
))}
)} {/* Access Info */} {order.payment_status === "paid" && (

Pembayaran berhasil! Akses produk Anda tersedia di halaman{" "} Akses Saya .

)}
); }