Fix waiting time loading

This commit is contained in:
Roba Boru
2026-07-16 16:41:36 +03:00
parent 842dc92a5a
commit f9b87b9911

View File

@@ -7,7 +7,7 @@ import { useBookingStore } from "@/lib/booking-store";
import { usePaymentStore } from "@/lib/payment-store";
import { useQuery } from "@tanstack/react-query";
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 { format } from "date-fns";
import { isChild, isFirstChild } from "@/utils/fare-utils";
@@ -60,6 +60,27 @@ export default function ConfirmationPage() {
const [copied, setCopied] = 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
// `await import(...)` resolves near-instantly — on iOS Safari, a file save triggered
// too long after the originating click's synchronous execution window is silently
@@ -92,15 +113,20 @@ export default function ConfirmationPage() {
enabled: !!bookingId,
});
// Poll the payment intent every 10 s while the booking is PENDING_PAYMENT.
// The backend auto-confirms (and generates tickets) 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.
// Poll the payment intent while the booking is PENDING_PAYMENT — fast during the
// grace period (catches a webhook that's just a few seconds behind), then slower
// in the background afterward. The backend auto-confirms (and generates tickets)
// 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>({
queryKey: ["payment-intent-status", bookingId],
queryFn: () => apiClient.get(`/payments/intents/${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(() => {
@@ -279,8 +305,11 @@ export default function ConfirmationPage() {
// without this, _booking is briefly undefined on first load, isConfirmed reads
// as false, and the page flashes "payment pending" before flipping to
// "confirmed" once the fetch resolves (common, since the payment webhook has
// often already completed by the time the user lands here).
if (isBookingLoading) {
// often already completed by the time the user lands here). Also keep showing
// 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 (
<div className="booking-page">
<div className="container mx-auto px-4">