Skip to content

Commit a8d8128

Browse files
committed
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
1 parent fe20451 commit a8d8128

File tree

1 file changed

+62
-4
lines changed

1 file changed

+62
-4
lines changed

frontend/app/routes/payment-confirmation.tsx

Lines changed: 62 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { validatePaymentParams } from '~/utils/validate.server'
1111
export const meta: MetaFunction = () => {
1212
return [
1313
{ title: 'Grant Interaction' },
14-
{ name: 'description', content: 'Interaction success' },
14+
{ name: 'description', content: 'Grant interaction result' },
1515
]
1616
}
1717

@@ -20,6 +20,19 @@ export async function loader({ request, context }: LoaderFunctionArgs) {
2020
const url = new URL(request.url)
2121
const params = Object.fromEntries([...url.searchParams])
2222

23+
// Check for interaction result parameters (e.g., result=grant_rejected)
24+
const interactionResult = params.result
25+
if (interactionResult && interactionResult !== 'grant_approved') {
26+
return data({
27+
success: false,
28+
error:
29+
interactionResult === 'grant_rejected'
30+
? 'The grant request was declined.'
31+
: `Interaction failed: ${interactionResult}`,
32+
params,
33+
})
34+
}
35+
2336
const validation = validatePaymentParams(params)
2437
if (!validation.success) {
2538
return data({
@@ -58,19 +71,64 @@ export async function loader({ request, context }: LoaderFunctionArgs) {
5871
}
5972
}
6073

61-
export default function PaymentComplete() {
62-
const { params } = useLoaderData<typeof loader>()
74+
export default function GrantInteraction() {
75+
const loaderData = useLoaderData<typeof loader>()
6376
const hasPostedMessage = useRef(false)
77+
const isSuccess = loaderData.success
6478

6579
useEffect(() => {
6680
if (hasPostedMessage.current) return
6781

6882
hasPostedMessage.current = true
6983
if (window.opener) {
70-
window.opener.postMessage({ type: 'GRANT_INTERACTION', ...params }, '*')
84+
window.opener.postMessage(
85+
{ type: 'GRANT_INTERACTION', ...loaderData.params },
86+
'*',
87+
)
7188
}
7289
}, [])
7390

91+
if (!isSuccess) {
92+
return (
93+
<div className="min-h-screen flex items-center justify-center bg-gray-50">
94+
<div className="max-w-md w-full space-y-8 p-8">
95+
<div className="text-center">
96+
<div className="mx-auto flex items-center justify-center h-24 w-24 rounded-full bg-red-100 mb-6">
97+
<svg
98+
className="h-12 w-12 text-red-600"
99+
fill="none"
100+
stroke="currentColor"
101+
viewBox="0 0 24 24"
102+
xmlns="http://www.w3.org/2000/svg"
103+
>
104+
<path
105+
strokeLinecap="round"
106+
strokeLinejoin="round"
107+
strokeWidth={2}
108+
d="M6 18L18 6M6 6l12 12"
109+
/>
110+
</svg>
111+
</div>
112+
113+
<h2 className="text-3xl font-bold text-gray-900 mb-4">
114+
Interaction Failed
115+
</h2>
116+
117+
<p className="text-gray-600 mb-8">
118+
{'error' in loaderData && loaderData.error
119+
? loaderData.error
120+
: 'Something went wrong. Please try again.'}
121+
</p>
122+
123+
<p className="text-gray-400 text-sm">
124+
You can close this window.
125+
</p>
126+
</div>
127+
</div>
128+
</div>
129+
)
130+
}
131+
74132
return (
75133
<div className="min-h-screen flex items-center justify-center bg-gray-50">
76134
<div className="max-w-md w-full space-y-8 p-8">

0 commit comments

Comments
 (0)