feat/payment-link-flow #13
|
|
@ -4,7 +4,8 @@ import { CheckoutPage } from '../pages/CheckoutPage'
|
|||
import { PaymentStatusPage } from '../pages/PaymentStatusPage'
|
||||
import { PaymentHistoryPage } from '../pages/PaymentHistoryPage'
|
||||
import { NotFoundPage } from '../pages/NotFoundPage'
|
||||
import { DemoStorePage } from '../pages/DemoStorePage'
|
||||
// import { DemoStorePage } from '../pages/DemoStorePage'
|
||||
import { InitPage } from '../pages/InitialPage'
|
||||
import { PayPage } from '../pages/PayPage'
|
||||
|
||||
const router = createBrowserRouter([
|
||||
|
|
@ -13,7 +14,7 @@ const router = createBrowserRouter([
|
|||
element: <AppLayout />,
|
||||
errorElement: <div role="alert">Terjadi kesalahan. Coba muat ulang.</div>,
|
||||
children: [
|
||||
{ index: true, element: <DemoStorePage /> },
|
||||
{ index: true, element: <InitPage /> },
|
||||
{ path: 'checkout', element: <CheckoutPage /> },
|
||||
{ path: 'pay/:token', element: <PayPage /> },
|
||||
{ path: 'payments/:orderId/status', element: <PaymentStatusPage /> },
|
||||
|
|
|
|||
|
|
@ -64,22 +64,21 @@ export function PaymentSheet({ merchantName = 'Simaya', orderId, amount, expireA
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{/* Amount panel */}
|
||||
|
||||
{expanded && (
|
||||
<div className="p-4 border-b border-black/10 flex items-start justify-between">
|
||||
<div>
|
||||
<div className="text-xs text-black/60">Total</div>
|
||||
<div className="text-xs text-black">Total</div>
|
||||
<div className="text-xl font-semibold">{formatCurrencyIDR(amount)}</div>
|
||||
<div className="text-xs text-black/60">Order ID #{orderId}</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{/* Body */}
|
||||
|
||||
<div className="p-4">
|
||||
{children}
|
||||
<TrustStrip location="sheet" />
|
||||
</div>
|
||||
{/* Sticky CTA (mobile-friendly) */}
|
||||
{showStatusCTA && (
|
||||
<div className="sticky bottom-0 bg-white/95 backdrop-blur border-t border-black/10 p-3 pb-[env(safe-area-inset-bottom)]">
|
||||
<Link
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './styles/globals.css'
|
||||
// Force light theme in case hosting injects a global 'dark' class
|
||||
|
||||
(() => {
|
||||
const html = document.documentElement
|
||||
try {
|
||||
|
|
@ -9,11 +9,9 @@ import './styles/globals.css'
|
|||
html.classList.remove('dark')
|
||||
}
|
||||
document.body.classList.remove('dark')
|
||||
// Hint browsers to prefer light color scheme
|
||||
html.style.colorScheme = 'light'
|
||||
html.setAttribute('data-theme', 'light')
|
||||
} catch {
|
||||
// noop
|
||||
}
|
||||
})()
|
||||
import { AppRouter } from './app/router'
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
export function InitPage() {
|
||||
return (
|
||||
<div className="fixed inset-0 bg-white overflow-hidden flex items-center justify-center p-6">
|
||||
<div className="flex items-center gap-3">
|
||||
<img src="/simaya.png" alt="Simaya" className="h-12 w-12 md:h-16 md:w-16" />
|
||||
<div className="text-black">
|
||||
<div className="text-xl font-semibold leading-none">Simaya Midtrans</div>
|
||||
<div className="text-sm leading-none">Payment Service</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ export function PayPage() {
|
|||
}
|
||||
}, [token])
|
||||
|
||||
const merchantName = useMemo(() => 'Simaya Retail', [])
|
||||
const merchantName = useMemo(() => '', [])
|
||||
|
||||
const isExpired = expireAt ? Date.now() > expireAt : false
|
||||
const enabledMap: Record<PaymentMethod, boolean> = useMemo(() => {
|
||||
|
|
@ -88,16 +88,17 @@ export function PayPage() {
|
|||
>
|
||||
<div className="space-y-4 px-4 py-6">
|
||||
<Alert title={title}>{msg}</Alert>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex flex-col gap-2 sm:flex-row">
|
||||
<Button
|
||||
variant="secondary"
|
||||
onClick={() => { try { window.location.reload() } catch { } }}
|
||||
className="w-full sm:w-auto"
|
||||
>
|
||||
Muat ulang
|
||||
</Button>
|
||||
<a
|
||||
href="mailto:retailimaya@gmail.com?subject=Permintaan%20Link%20Pembayaran&body=Order%20ID:%20"
|
||||
className="inline-flex items-center px-3 py-2 rounded border bg-gray-800 !text-white hover:!text-white focus:!text-white visited:!text-white active:!text-white"
|
||||
className="inline-flex items-center px-3 py-2 rounded border bg-gray-800 !text-white hover:!text-white focus:!text-white visited:!text-white active:!text-white w-full sm:w-auto justify-center"
|
||||
>
|
||||
Hubungi Admin
|
||||
</a>
|
||||
|
|
@ -123,7 +124,6 @@ export function PayPage() {
|
|||
onSelect={(m) => {
|
||||
setSelectedMethod(m as Method)
|
||||
if (m === 'bank_transfer' || m === 'cstore') {
|
||||
// Panel pemilihan bank/toko akan muncul di bawah item, dan lanjut ke step 3 setelah memilih
|
||||
} else if (m === 'cpay') {
|
||||
try {
|
||||
window.open('https://play.google.com/store/apps/details?id=com.cifo.walanja', '_blank')
|
||||
|
|
@ -148,7 +148,7 @@ export function PayPage() {
|
|||
return (
|
||||
<div className="space-y-2" aria-live="polite">
|
||||
<div className="text-xs text-gray-600">Pilih bank untuk membuat Virtual Account</div>
|
||||
<div className={`grid grid-cols-3 gap-2 ${isBusy ? 'pointer-events-none opacity-60' : ''}`}>
|
||||
<div className={`grid grid-cols-2 md:grid-cols-3 gap-2 ${isBusy ? 'pointer-events-none opacity-60' : ''}`}>
|
||||
{(['bca', 'bni', 'bri', 'cimb', 'mandiri', 'permata'] as BankKey[]).map((bk) => (
|
||||
<button
|
||||
key={bk}
|
||||
|
|
@ -158,7 +158,7 @@ export function PayPage() {
|
|||
setIsBusy(true)
|
||||
setTimeout(() => { setCurrentStep(3); setIsBusy(false) }, 300)
|
||||
}}
|
||||
className="rounded border border-gray-300 bg-white p-2 flex items-center justify-center overflow-hidden hover:bg-gray-100"
|
||||
className="rounded border border-gray-300 bg-white p-3 md:p-2 w-full flex items-center justify-center overflow-hidden hover:bg-gray-100"
|
||||
aria-label={`Pilih bank ${bk.toUpperCase()}`}
|
||||
>
|
||||
<BankLogo bank={bk} />
|
||||
|
|
@ -188,7 +188,7 @@ export function PayPage() {
|
|||
setIsBusy(true)
|
||||
setTimeout(() => { setCurrentStep(3); setIsBusy(false) }, 300)
|
||||
}}
|
||||
className="rounded border border-gray-300 bg-white p-2 flex items-center justify-center hover:bg-gray-100"
|
||||
className="rounded border border-gray-300 bg-white p-3 md:p-2 w-full flex items-center justify-center hover:bg-gray-100"
|
||||
aria-label={`Pilih toko ${st.toUpperCase()}`}
|
||||
>
|
||||
{st === 'alfamart' ? <LogoAlfamart /> : <LogoIndomaret />}
|
||||
|
|
|
|||
|
|
@ -129,8 +129,6 @@ export async function getPaymentLinkPayload(token: string): Promise<PaymentLinkP
|
|||
Logger.info('paymentlink.resolve', { tokenLen: token.length })
|
||||
return data as PaymentLinkPayload
|
||||
}
|
||||
// Fallback when API base not set or resolver unavailable
|
||||
// Try a best-effort decode of base64(JSON) payload; if fails, use defaults
|
||||
try {
|
||||
const json = JSON.parse(atob(token))
|
||||
return {
|
||||
|
|
|
|||
Loading…
Reference in New Issue