const AccountPage = ({ user, onLogout, setPage, lang }) => { const t = (en, id) => lang === "id" ? id : en; const [tab, setTab] = React.useState("profile"); const [profile, setProfile] = React.useState({ name: user?.name || "", phone: "" }); const [address, setAddress] = React.useState({ address:"", district:"", city:"", province:"", postal:"", area_id:"" }); const [savedAddr, setSavedAddr] = React.useState(null); const [password, setPassword] = React.useState({ old:"", new1:"", new2:"" }); const [orders, setOrders] = React.useState([]); const [loading, setLoading] = React.useState(false); const [msg, setMsg] = React.useState({ text:"", ok:true }); // Modal states const [trackingModal, setTrackingModal] = React.useState(null); // { order, tracking, loading } const [paying, setPaying] = React.useState(null); // order_number being paid const STATUS_LABEL = { pending:"Menunggu Bayar", paid:"Dibayar", confirmed:"Dikonfirmasi", processing:"Diproses", shipped:"Dikirim", delivered:"Selesai", cancelled:"Dibatalkan" }; const STATUS_COLOR = { pending:"#f59e0b", paid:"#10b981", confirmed:"#3b82f6", processing:"#8b5cf6", shipped:"#06b6d4", delivered:"#22c55e", cancelled:"#ef4444" }; const fmt = n => "Rp" + Number(n).toLocaleString("id-ID"); const fmtD = d => new Date(d).toLocaleDateString("id-ID", { day:"numeric", month:"short", year:"numeric" }); const fmtDT = d => new Date(d).toLocaleString("id-ID", { day:"numeric", month:"short", hour:"2-digit", minute:"2-digit" }); const showMsg = (text, ok=true) => { setMsg({text,ok}); setTimeout(()=>setMsg({text:"",ok:true}),3000); }; // Load profil & alamat React.useEffect(() => { fetch("api/account.php", { credentials:"include" }) .then(r => r.json()) .then(d => { if (d.user) { setProfile({ name: d.user.name||"", phone: d.user.phone||"" }); if (d.user.saved_address) { setSavedAddr(d.user.saved_address); setAddress(prev => ({ ...prev, ...d.user.saved_address })); } } }).catch(() => {}); }, []); // Load orders const loadOrders = () => { setLoading(true); fetch(`api/order.php?email=${encodeURIComponent(user?.email||"")}`, { credentials:"include" }) .then(r => r.json()) .then(d => setOrders(d.orders || [])) .catch(() => {}) .finally(() => setLoading(false)); }; React.useEffect(() => { if (tab === "orders") loadOrders(); }, [tab]); const saveProfile = async () => { setLoading(true); const res = await fetch("api/account.php", { method:"POST", credentials:"include", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ action:"update_profile", name:profile.name, phone:profile.phone }) }); const d = await res.json(); setLoading(false); if (d.success) showMsg("✓ Profil berhasil disimpan!"); else showMsg("❌ " + (d.error||"Gagal — coba login ulang"), false); }; const saveAddress = async () => { setLoading(true); const res = await fetch("api/account.php", { method:"POST", credentials:"include", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ action:"save_address", address:address.address, district:address.district, city:address.city, province:address.province, postal:address.postal, area_id:address.area_id||"" }) }); const d = await res.json(); setLoading(false); if (d.success) { setSavedAddr({...address}); showMsg("✓ Alamat berhasil disimpan!"); } else showMsg("❌ " + (d.error||"Gagal"), false); }; const changePassword = async () => { if (password.new1 !== password.new2) { showMsg("❌ Password baru tidak cocok", false); return; } if (password.new1.length < 6) { showMsg("❌ Password minimal 6 karakter", false); return; } setLoading(true); const res = await fetch("api/account.php", { method:"POST", credentials:"include", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ action:"change_password", old_password:password.old, new_password:password.new1 }) }); const d = await res.json(); setLoading(false); if (d.success) { setPassword({old:"",new1:"",new2:""}); showMsg("✓ Password berhasil diubah!"); } else showMsg("❌ " + (d.error||"Gagal"), false); }; // ── Pay pending order (re-open Duitku) ──────────────── const payNow = async (order_number) => { setPaying(order_number); try { const r = await fetch("api/payment.php", { method:"POST", headers:{"Content-Type":"application/json"}, body: JSON.stringify({ order_number }) }); const d = await r.json(); if (!d.success || !d.reference) { showMsg("❌ Gagal init pembayaran: " + (d.error || ""), false); setPaying(null); return; } // Load Duitku POP script if (!window.checkout || typeof window.checkout.process !== "function") { await new Promise((resolve, reject) => { const s = document.createElement("script"); s.src = d.popUrl || "https://app-sandbox.duitku.com/lib/js/duitku.js"; s.onload = resolve; s.onerror = () => reject(new Error("Gagal load Duitku")); document.head.appendChild(s); }); } window.checkout.process(d.reference, { successEvent: () => { showMsg("✓ Pembayaran berhasil!"); loadOrders(); setPaying(null); }, pendingEvent: () => { showMsg("⏳ Pembayaran pending..."); loadOrders(); setPaying(null); }, errorEvent: (e) => { showMsg("❌ " + (e?.message || "Pembayaran gagal"), false); setPaying(null); }, closeEvent: () => setPaying(null), }); } catch (e) { showMsg("❌ " + e.message, false); setPaying(null); } }; // ── Open tracking modal ─────────────────────────────── const openTracking = async (order) => { setTrackingModal({ order, tracking:null, loading:true }); try { const r = await fetch(`api/order.php?track=${encodeURIComponent(order.order_number)}`); const d = await r.json(); setTrackingModal({ order: d.order || order, tracking: d.tracking, loading:false }); } catch (e) { setTrackingModal({ order, tracking:null, loading:false }); } }; // ── Countdown timer untuk order pending ──────────────── const [now, setNow] = React.useState(Date.now()); React.useEffect(() => { if (tab !== "orders") return; const i = setInterval(() => setNow(Date.now()), 1000); return () => clearInterval(i); }, [tab]); const getCountdown = (created_at, expireMinutes = 60) => { const created = new Date(created_at).getTime(); const expire = created + expireMinutes * 60 * 1000; const left = expire - now; if (left <= 0) return null; const mm = Math.floor(left / 60000); const ss = Math.floor((left % 60000) / 1000); return `${String(mm).padStart(2,"0")}:${String(ss).padStart(2,"0")}`; }; // ── Timeline steps ──────────────────────────────────── const TIMELINE_STEPS = [ { key:"paid", label:"Dibayar" }, { key:"processing", label:"Diproses" }, { key:"shipped", label:"Dikirim" }, { key:"delivered", label:"Diterima" }, ]; const getStepIndex = (status) => { if (status === "delivered") return 4; if (status === "shipped") return 3; if (status === "processing" || status === "confirmed") return 2; if (status === "paid") return 1; return 0; }; // ── Helper: check if order has PO items ──────────────── const isPoOrder = (o) => o.tracking_status === "waiting_production"; // ── Styles ──────────────────────────────────────────── const inp = { width:"100%", padding:"11px 14px", border:"1px solid #e0deda", background:"#fafaf8", fontFamily:"'DM Sans',sans-serif", fontSize:13, color:"#111", outline:"none", borderRadius:2 }; const lbl = { display:"block", fontSize:10, letterSpacing:"0.12em", textTransform:"uppercase", color:"#888", fontWeight:600, marginBottom:6 }; const btn = (bg="#111",cl="#f9f8f6") => ({ background:bg, color:cl, border:"none", cursor:"pointer", fontFamily:"'DM Sans',sans-serif", fontSize:11, letterSpacing:"0.1em", textTransform:"uppercase", fontWeight:600, padding:"11px 28px", borderRadius:2 }); const tabStyle = (active) => ({ flex: "1 1 auto", minWidth: 0, padding: "12px 8px", background: "none", border: "none", borderBottom: active ? "2px solid #111" : "2px solid transparent", fontFamily: "'DM Sans',sans-serif", fontSize: 10, letterSpacing: "0.05em", textTransform: "uppercase", color: active ? "#111" : "#aaa", cursor: "pointer", fontWeight: active ? 600 : 400, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }); // ── Single Order Card renderer ──────────────────────── const renderOrderCard = (o) => { const isPo = isPoOrder(o); const isPending = o.status === "pending"; const countdown = isPending ? getCountdown(o.created_at, 60) : null; const stepIdx = getStepIndex(o.status); const showTimeline = ["paid","processing","confirmed","shipped","delivered"].includes(o.status); const items = o.items || []; return (
{/* Header */}

{o.order_number}

{fmtD(o.created_at)}

{STATUS_LABEL[o.status]||o.status} {isPo && ( ⏳ PO )}
{/* Pending — countdown + pay button */} {isPending && (
{countdown ? ( <>

⏱ {t("Pay within", "Bayar dalam")}: {countdown}

) : (

⚠ {t("This order has expired", "Pesanan ini sudah expired")}

)}
)} {/* PO Banner */} {isPo && o.status === "paid" && (
⏳ {t( "Your order is being produced. We'll notify you once it's ready to ship.", "Pesanan kamu sedang dibuat. Kami akan kabari setelah siap dikirim." )}
)} {/* Visual Timeline (untuk order paid+) */} {showTimeline && !isPo && (
{TIMELINE_STEPS.map((step, i) => { const active = i < stepIdx; const current = i === stepIdx - 1; return (
{active ? "✓" : (i+1)}

{step.label}

{i < TIMELINE_STEPS.length - 1 && (
)} ); })}
)} {/* Items */}
{items.map((it, idx) => (
0 ? "1px solid #f0efed" : "none" }}> {it.image && ( {it.name} )}

{it.name}

{it.selectedColor && `${it.selectedColor} · `} {t("Qty", "Jumlah")}: {it.qty || it.quantity || 1}

{fmt((it.price||0) * (it.qty||it.quantity||1))}

))}
{/* Footer */}
{o.courier_name && {o.courier_name.toUpperCase()} {o.courier_service?.toUpperCase()}} {o.waybill_id && ( · {t("Tracking", "Resi")}: {o.waybill_id} )}

{fmt(o.total)}

{o.waybill_id && ( )}
); }; return (
{/* Header */}

Akun Saya

{user?.email}

{/* Toast */} {msg.text && (
{msg.text}
)}
{/* Tabs */}
{[["profile","👤 Profil"],["orders","🛍 Pesanan"],["address","📍 Alamat"],["password","🔑 Password"]].map(([id,label]) => ( ))}
{/* ── Profil ── */} {tab === "profile" && (

Informasi Profil

setProfile(p => ({...p, name:e.target.value}))} placeholder="Nama kamu" />
setProfile(p => ({...p, phone:e.target.value}))} placeholder="08xx xxxx xxxx" />

Email tidak bisa diubah

)} {/* ── Pesanan (UPGRADED) ── */} {tab === "orders" && (

Riwayat Pesanan

{loading &&

Memuat...

} {!loading && orders.length === 0 && (

📦

Belum ada pesanan

)} {!loading && orders.map(renderOrderCard)}
)} {/* ── Alamat ── */} {tab === "address" && (

Alamat Tersimpan

Alamat ini akan otomatis terisi saat checkout

{savedAddr && (

Alamat Tersimpan

{savedAddr.address}
{savedAddr.district && savedAddr.district + ", "}{savedAddr.city}
{savedAddr.province && savedAddr.province + " "}{savedAddr.postal}
)}
setAddress(a => ({...a, address:e.target.value}))} placeholder="Jalan, No., RT/RW" />
setAddress(a => ({...a, city, district, province, postal, area_id}))} />
)} {/* ── Password ── */} {tab === "password" && (

Ganti Password

setPassword(p => ({...p, old:e.target.value}))} placeholder="••••••••" />
setPassword(p => ({...p, new1:e.target.value}))} placeholder="Min. 6 karakter" />
setPassword(p => ({...p, new2:e.target.value}))} placeholder="••••••••" />
)}
{/* ── Tracking Modal ──────────────────────────────────── */} {trackingModal && (
setTrackingModal(null)} style={{ position:"fixed", inset:0, background:"rgba(0,0,0,0.5)", zIndex:9999, display:"flex", alignItems:"center", justifyContent:"center", padding:20 }}>
e.stopPropagation()} style={{ background:"#fff", borderRadius:6, maxWidth:520, width:"100%", maxHeight:"85vh", overflow:"auto", boxShadow:"0 20px 50px rgba(0,0,0,0.3)" }}> {/* Header */}

Lacak Paket

{trackingModal.order.order_number}

{/* Info pengiriman */}
Kurir: {trackingModal.order.courier_name?.toUpperCase()} {trackingModal.order.courier_service?.toUpperCase()}
No. Resi: {trackingModal.order.waybill_id}
{/* Timeline */}
{trackingModal.loading &&

Memuat tracking dari kurir...

} {!trackingModal.loading && (!trackingModal.tracking || !trackingModal.tracking.history || trackingModal.tracking.history.length === 0) && (

📭

{t("No tracking history yet", "Belum ada riwayat tracking")}

{t("Try again in a few hours", "Coba lagi dalam beberapa jam")}

)} {!trackingModal.loading && trackingModal.tracking?.history?.length > 0 && (
{/* Vertical line */}
{trackingModal.tracking.history.map((h, i) => (

{h.updated_at ? fmtDT(h.updated_at) : ""}

{h.note || h.status || "-"}

))}
)}
)}
); }; Object.assign(window, { AccountPage });