mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 09:58:12 +00:00
Fix waiting time loading
This commit is contained in:
@@ -7,7 +7,7 @@ import { useBookingStore } from "@/lib/booking-store";
|
|||||||
import { usePaymentStore } from "@/lib/payment-store";
|
import { usePaymentStore } from "@/lib/payment-store";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { apiClient } from "@/lib/api-client";
|
import { apiClient } from "@/lib/api-client";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { CheckCircle, Clock, Copy, Train, FileText } from "lucide-react";
|
import { CheckCircle, Clock, Copy, Train, FileText } from "lucide-react";
|
||||||
import { format } from "date-fns";
|
import { format } from "date-fns";
|
||||||
import { isChild, isFirstChild } from "@/utils/fare-utils";
|
import { isChild, isFirstChild } from "@/utils/fare-utils";
|
||||||
@@ -60,6 +60,27 @@ export default function ConfirmationPage() {
|
|||||||
const [copied, setCopied] = useState(false);
|
const [copied, setCopied] = useState(false);
|
||||||
const [isGeneratingVoucher, setIsGeneratingVoucher] = useState(false);
|
const [isGeneratingVoucher, setIsGeneratingVoucher] = useState(false);
|
||||||
|
|
||||||
|
// Grace period after landing on this page: keep showing the generic "processing"
|
||||||
|
// spinner instead of the "payment pending" screen, and poll the payment intent
|
||||||
|
// frequently — booking.status and paymentIntent.status flip to CONFIRMED/SUCCEEDED
|
||||||
|
// together (see finalizePaymentSuccess in payments.service.ts), so a payment that
|
||||||
|
// already succeeded at the provider often just needs a few more seconds for its
|
||||||
|
// webhook to reach us. Once the grace period elapses, fall back to the normal
|
||||||
|
// pending screen with slower background polling.
|
||||||
|
const CONFIRMATION_GRACE_PERIOD_MS = 10_000;
|
||||||
|
const FAST_POLL_INTERVAL_MS = 2_500;
|
||||||
|
const SLOW_POLL_INTERVAL_MS = 10_000;
|
||||||
|
const mountTimeRef = useRef(Date.now());
|
||||||
|
const [withinGracePeriod, setWithinGracePeriod] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(
|
||||||
|
() => setWithinGracePeriod(false),
|
||||||
|
CONFIRMATION_GRACE_PERIOD_MS,
|
||||||
|
);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Warms the code-split voucher module ahead of the click so the handler's own
|
// Warms the code-split voucher module ahead of the click so the handler's own
|
||||||
// `await import(...)` resolves near-instantly — on iOS Safari, a file save triggered
|
// `await import(...)` resolves near-instantly — on iOS Safari, a file save triggered
|
||||||
// too long after the originating click's synchronous execution window is silently
|
// too long after the originating click's synchronous execution window is silently
|
||||||
@@ -92,15 +113,20 @@ export default function ConfirmationPage() {
|
|||||||
enabled: !!bookingId,
|
enabled: !!bookingId,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Poll the payment intent every 10 s while the booking is PENDING_PAYMENT.
|
// Poll the payment intent while the booking is PENDING_PAYMENT — fast during the
|
||||||
// The backend auto-confirms (and generates tickets) when the payment-api reports
|
// grace period (catches a webhook that's just a few seconds behind), then slower
|
||||||
// SUCCEEDED, so detecting that here means the booking is now CONFIRMED — refetch
|
// in the background afterward. The backend auto-confirms (and generates tickets)
|
||||||
// to update the UI without requiring the user to refresh.
|
// when the payment-api reports SUCCEEDED, so detecting that here means the
|
||||||
|
// booking is now CONFIRMED — refetch to update the UI without requiring the user
|
||||||
|
// to refresh.
|
||||||
const { data: intentStatus } = useQuery<any>({
|
const { data: intentStatus } = useQuery<any>({
|
||||||
queryKey: ["payment-intent-status", bookingId],
|
queryKey: ["payment-intent-status", bookingId],
|
||||||
queryFn: () => apiClient.get(`/payments/intents/${bookingId}`),
|
queryFn: () => apiClient.get(`/payments/intents/${bookingId}`),
|
||||||
enabled: _booking?.status === "PENDING_PAYMENT" && !!bookingId,
|
enabled: _booking?.status === "PENDING_PAYMENT" && !!bookingId,
|
||||||
refetchInterval: 10_000,
|
refetchInterval: () =>
|
||||||
|
Date.now() - mountTimeRef.current < CONFIRMATION_GRACE_PERIOD_MS
|
||||||
|
? FAST_POLL_INTERVAL_MS
|
||||||
|
: SLOW_POLL_INTERVAL_MS,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -279,8 +305,11 @@ export default function ConfirmationPage() {
|
|||||||
// without this, _booking is briefly undefined on first load, isConfirmed reads
|
// without this, _booking is briefly undefined on first load, isConfirmed reads
|
||||||
// as false, and the page flashes "payment pending" before flipping to
|
// as false, and the page flashes "payment pending" before flipping to
|
||||||
// "confirmed" once the fetch resolves (common, since the payment webhook has
|
// "confirmed" once the fetch resolves (common, since the payment webhook has
|
||||||
// often already completed by the time the user lands here).
|
// often already completed by the time the user lands here). Also keep showing
|
||||||
if (isBookingLoading) {
|
// this same spinner through the grace period above if the booking is still
|
||||||
|
// PENDING_PAYMENT — most of the time the webhook lands within that window, so
|
||||||
|
// the user goes straight to "confirmed" without ever seeing "pending" at all.
|
||||||
|
if (isBookingLoading || (withinGracePeriod && _booking?.status === "PENDING_PAYMENT")) {
|
||||||
return (
|
return (
|
||||||
<div className="booking-page">
|
<div className="booking-page">
|
||||||
<div className="container mx-auto px-4">
|
<div className="container mx-auto px-4">
|
||||||
|
|||||||
Reference in New Issue
Block a user