From a8d8128c4926fe00f4dc01e6ba8bd4904741c059 Mon Sep 17 00:00:00 2001 From: Onyx2406 Date: Fri, 3 Apr 2026 08:00:34 +0000 Subject: [PATCH] fix: handle declined and error states in grant interaction page The grant interaction page (payment-confirmation.tsx) previously showed the success UI with a green checkmark for all states, including declined grants and server errors. Users saw "Payment Complete!" even when their grant was rejected. Changes: - Check the `result` query parameter for interaction outcome (grant_rejected, etc.) and display appropriate error UI - Use the loader's `success` field to conditionally render success or error state with distinct icons and messaging - Rename the component from PaymentComplete to GrantInteraction to better reflect its purpose - Display the specific error message from the loader Closes #480 --- frontend/app/routes/payment-confirmation.tsx | 66 ++++++++++++++++++-- 1 file changed, 62 insertions(+), 4 deletions(-) diff --git a/frontend/app/routes/payment-confirmation.tsx b/frontend/app/routes/payment-confirmation.tsx index e50ed33c8..a2f0316f4 100644 --- a/frontend/app/routes/payment-confirmation.tsx +++ b/frontend/app/routes/payment-confirmation.tsx @@ -11,7 +11,7 @@ import { validatePaymentParams } from '~/utils/validate.server' export const meta: MetaFunction = () => { return [ { title: 'Grant Interaction' }, - { name: 'description', content: 'Interaction success' }, + { name: 'description', content: 'Grant interaction result' }, ] } @@ -20,6 +20,19 @@ export async function loader({ request, context }: LoaderFunctionArgs) { const url = new URL(request.url) const params = Object.fromEntries([...url.searchParams]) + // Check for interaction result parameters (e.g., result=grant_rejected) + const interactionResult = params.result + if (interactionResult && interactionResult !== 'grant_approved') { + return data({ + success: false, + error: + interactionResult === 'grant_rejected' + ? 'The grant request was declined.' + : `Interaction failed: ${interactionResult}`, + params, + }) + } + const validation = validatePaymentParams(params) if (!validation.success) { return data({ @@ -58,19 +71,64 @@ export async function loader({ request, context }: LoaderFunctionArgs) { } } -export default function PaymentComplete() { - const { params } = useLoaderData() +export default function GrantInteraction() { + const loaderData = useLoaderData() const hasPostedMessage = useRef(false) + const isSuccess = loaderData.success useEffect(() => { if (hasPostedMessage.current) return hasPostedMessage.current = true if (window.opener) { - window.opener.postMessage({ type: 'GRANT_INTERACTION', ...params }, '*') + window.opener.postMessage( + { type: 'GRANT_INTERACTION', ...loaderData.params }, + '*', + ) } }, []) + if (!isSuccess) { + return ( +
+
+
+
+ + + +
+ +

+ Interaction Failed +

+ +

+ {'error' in loaderData && loaderData.error + ? loaderData.error + : 'Something went wrong. Please try again.'} +

+ +

+ You can close this window. +

+
+
+
+ ) + } + return (