feat(payments): complete Story 1.1 - Add loading overlay and error messages to all payment panels
- Updated GoPayPanel with LoadingOverlay and mapErrorToUserMessage - Updated CStorePanel with LoadingOverlay and mapErrorToUserMessage - All 3 payment methods now show user-friendly Bahasa Indonesia error messages - Full-screen loading overlay prevents duplicate payment code generation Story: 1.1 - Prevent Duplicate VA/QR/Code Generation & Improve Feedback Status: Complete
This commit is contained in:
parent
4eccff2c03
commit
d08b0bd312
|
|
@ -5,6 +5,8 @@ import React from 'react'
|
||||||
import { PaymentInstructions } from './PaymentInstructions'
|
import { PaymentInstructions } from './PaymentInstructions'
|
||||||
import { postCharge } from '../../../services/api'
|
import { postCharge } from '../../../services/api'
|
||||||
import { InlinePaymentStatus } from './InlinePaymentStatus'
|
import { InlinePaymentStatus } from './InlinePaymentStatus'
|
||||||
|
import { LoadingOverlay } from '../../../components/LoadingOverlay'
|
||||||
|
import { mapErrorToUserMessage } from '../../../lib/errorMessages'
|
||||||
|
|
||||||
type StoreKey = 'alfamart' | 'indomaret'
|
type StoreKey = 'alfamart' | 'indomaret'
|
||||||
|
|
||||||
|
|
@ -36,7 +38,7 @@ export function CStorePanel({ orderId, amount, locked, onChargeInitiated, defaul
|
||||||
if (typeof res?.store === 'string') setStoreFromRes(res.store)
|
if (typeof res?.store === 'string') setStoreFromRes(res.store)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) toast.error(`Gagal membuat kode pembayaran: ${(e as Error).message}`)
|
if (!cancelled) toast.error(mapErrorToUserMessage(e))
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setBusy(false)
|
if (!cancelled) setBusy(false)
|
||||||
cstoreTasks.delete(chargeKey)
|
cstoreTasks.delete(chargeKey)
|
||||||
|
|
@ -60,7 +62,7 @@ export function CStorePanel({ orderId, amount, locked, onChargeInitiated, defaul
|
||||||
if (typeof res?.store === 'string') setStoreFromRes(res.store)
|
if (typeof res?.store === 'string') setStoreFromRes(res.store)
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) toast.error(`Gagal membuat kode pembayaran: ${(e as Error).message}`)
|
if (!cancelled) toast.error(mapErrorToUserMessage(e))
|
||||||
attemptedCStoreKeys.delete(chargeKey)
|
attemptedCStoreKeys.delete(chargeKey)
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) setBusy(false)
|
if (!cancelled) setBusy(false)
|
||||||
|
|
@ -78,6 +80,8 @@ export function CStorePanel({ orderId, amount, locked, onChargeInitiated, defaul
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<LoadingOverlay isLoading={busy} message="Sedang membuat kode pembayaran..." />
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="font-medium">Convenience Store</div>
|
<div className="font-medium">Convenience Store</div>
|
||||||
{selected && (
|
{selected && (
|
||||||
|
|
@ -126,5 +130,6 @@ export function CStorePanel({ orderId, amount, locked, onChargeInitiated, defaul
|
||||||
<InlinePaymentStatus orderId={orderId} method="cstore" />
|
<InlinePaymentStatus orderId={orderId} method="cstore" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -6,6 +6,8 @@ import { GoPayLogosRow } from './PaymentLogos'
|
||||||
import { postCharge } from '../../../services/api'
|
import { postCharge } from '../../../services/api'
|
||||||
import { InlinePaymentStatus } from './InlinePaymentStatus'
|
import { InlinePaymentStatus } from './InlinePaymentStatus'
|
||||||
import { toast } from '../../../components/ui/toast'
|
import { toast } from '../../../components/ui/toast'
|
||||||
|
import { LoadingOverlay } from '../../../components/LoadingOverlay'
|
||||||
|
import { mapErrorToUserMessage } from '../../../lib/errorMessages'
|
||||||
|
|
||||||
// Global guards/tasks to stabilize QR generation across StrictMode remounts
|
// Global guards/tasks to stabilize QR generation across StrictMode remounts
|
||||||
const attemptedChargeKeys = new Set<string>()
|
const attemptedChargeKeys = new Set<string>()
|
||||||
|
|
@ -52,7 +54,9 @@ export function GoPayPanel({ orderId, amount, locked, onChargeInitiated }: { ord
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
|
<LoadingOverlay isLoading={busy} message="Sedang membuat kode QR..." />
|
||||||
<GoPayPanel_AutoEffect
|
<GoPayPanel_AutoEffect
|
||||||
orderId={orderId}
|
orderId={orderId}
|
||||||
amount={amount}
|
amount={amount}
|
||||||
|
|
@ -147,6 +151,7 @@ export function GoPayPanel({ orderId, amount, locked, onChargeInitiated }: { ord
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -175,7 +180,7 @@ export function GoPayPanel_AutoEffect({ orderId, amount, locked, mode, setBusy,
|
||||||
onChargeInitiated?.()
|
onChargeInitiated?.()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) toast.error(`Gagal membuat QR: ${(e as Error).message}`)
|
if (!cancelled) toast.error(mapErrorToUserMessage(e))
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
setBusy(false)
|
setBusy(false)
|
||||||
|
|
@ -204,7 +209,7 @@ export function GoPayPanel_AutoEffect({ orderId, amount, locked, mode, setBusy,
|
||||||
onChargeInitiated?.()
|
onChargeInitiated?.()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (!cancelled) toast.error(`Gagal membuat QR: ${(e as Error).message}`)
|
if (!cancelled) toast.error(mapErrorToUserMessage(e))
|
||||||
attemptedChargeKeys.delete(chargeKey)
|
attemptedChargeKeys.delete(chargeKey)
|
||||||
} finally {
|
} finally {
|
||||||
if (!cancelled) {
|
if (!cancelled) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue