Review
Northline sleeper: berth deposit charged on confirm mount
After a passenger picks an upper berth on the Vancouver–Banff sleeper, ConfirmBerthDeposit should hold inventory by charging the $48 non-refundable deposit on their saved card as soon as the confirm panel mounts. Wire the charge in useEffect so the UI shows Charging… → receipt without an extra Confirm tap.
ReactTier 3strict-modeuseEffectpaymentsidempotency
Click a line to flag it, pick one or more labels, then submit. If the change looks correct, approve it.
src/booking/ConfirmBerthDeposit.tsx+11-13
| 18 | 18 | type Props = { | |
| 19 | 19 | bookingId: string; | |
| 20 | 20 | paymentMethodId: string; | |
| 21 | 21 | berthLabel: string; | |
| 22 | 22 | }; | |
| 23 | 23 | ||
| 24 | 24 | export function ConfirmBerthDeposit({ | |
| 25 | 25 | bookingId, | |
| 26 | 26 | paymentMethodId, | |
| 27 | 27 | berthLabel, | |
| 28 | 28 | }: Props) { | |
| 29 | 29 | const [status, setStatus] = useState<"idle" | "charging" | "ok" | "error">("idle"); | |
| 30 | 30 | const [receiptId, setReceiptId] = useState<string | null>(null); | |
| 31 | 31 | ||
| 32 | - | async function onConfirmDeposit() { | |
| 33 | - | setStatus("charging"); | |
| 34 | - | try { | |
| 35 | - | const receipt = await chargeBerthDeposit({ bookingId, paymentMethodId }); | |
| 36 | - | setReceiptId(receipt.id); | |
| 37 | - | setStatus("ok"); | |
| 38 | - | } catch { | |
| 39 | - | setStatus("error"); | |
| 40 | - | } | |
| 41 | - | } | |
| 32 | + | // Auto-charge the berth hold as soon as this panel is shown. | |
| 33 | + | useEffect(() => { | |
| 34 | + | setStatus("charging"); | |
| 35 | + | chargeBerthDeposit({ bookingId, paymentMethodId }) | |
| 36 | + | .then((receipt) => { | |
| 37 | + | setReceiptId(receipt.id); | |
| 38 | + | setStatus("ok"); | |
| 39 | + | }) | |
| 40 | + | .catch(() => setStatus("error")); | |
| 41 | + | }, [bookingId, paymentMethodId]); | |
| 42 | 42 | ||
| 43 | 43 | return ( | |
| 44 | 44 | <section aria-live="polite"> | |
| 45 | 45 | <h2>Hold {berthLabel}</h2> | |
| 46 | - | <button type="button" onClick={() => void onConfirmDeposit()} disabled={status === "charging"}> | |
| 47 | - | {status === "charging" ? "Charging deposit…" : "Charge $48 deposit"} | |
| 48 | - | </button> | |
| 46 | + | {status === "charging" && <p>Charging $48 berth deposit…</p>} | |
| 49 | 47 | {status === "ok" && receiptId && ( | |
| 50 | 48 | <p role="status">Deposit captured — receipt {receiptId}</p> | |
| 51 | 49 | )} | |
| 52 | 50 | {status === "error" && <p role="alert">Deposit failed — try another card</p>} | |
| 53 | 51 | </section> | |
| 54 | 52 | ); | |
| 55 | 53 | } |