68 lines
2.4 KiB
TypeScript
68 lines
2.4 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useTranslations } from "next-intl";
|
|
|
|
/**
|
|
* Banner shown after a return from Stripe Checkout.
|
|
*
|
|
* ?paid=1 → green success banner. The webhook may or may
|
|
* not have processed yet, so we phrase the message
|
|
* as "Payment received, status will update shortly"
|
|
* and don't claim the status is already paid. A
|
|
* light auto-refresh after a few seconds nudges
|
|
* the page to pick up the new status badge.
|
|
*
|
|
* ?cancelled=1 → neutral grey banner: "Payment cancelled". The
|
|
* invoice stays in 'open' state.
|
|
*
|
|
* The banner cleans up the query params from the URL so a page
|
|
* reload doesn't repeat the message. We use router.replace() to
|
|
* keep history clean.
|
|
*/
|
|
export function PaymentStatusBanner() {
|
|
const router = useRouter();
|
|
const t = useTranslations("customerBilling");
|
|
const [state, setState] = useState<"paid" | "cancelled" | null>(null);
|
|
|
|
useEffect(() => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
if (params.has("paid")) {
|
|
setState("paid");
|
|
// The webhook usually arrives before the browser redirect
|
|
// completes, so the page often renders with status='paid'
|
|
// on first load and this refresh is a no-op. In the rare
|
|
// case where it arrives slightly after, a short refresh
|
|
// picks up the status flip. 1.5s is comfortable for both.
|
|
const timer = setTimeout(() => {
|
|
router.refresh();
|
|
}, 1500);
|
|
// Strip the query string out of the URL.
|
|
const cleanUrl = window.location.pathname;
|
|
window.history.replaceState({}, "", cleanUrl);
|
|
return () => clearTimeout(timer);
|
|
} else if (params.has("cancelled")) {
|
|
setState("cancelled");
|
|
const cleanUrl = window.location.pathname;
|
|
window.history.replaceState({}, "", cleanUrl);
|
|
}
|
|
}, [router]);
|
|
|
|
if (state === "paid") {
|
|
return (
|
|
<div className="mb-4 p-3 rounded-md border border-success bg-success/10 text-sm text-success">
|
|
{t("paymentReceived")}
|
|
</div>
|
|
);
|
|
}
|
|
if (state === "cancelled") {
|
|
return (
|
|
<div className="mb-4 p-3 rounded-md border border-border bg-surface-2 text-sm text-text-secondary">
|
|
{t("paymentCancelled")}
|
|
</div>
|
|
);
|
|
}
|
|
return null;
|
|
}
|