feat(client): refine payment handling and improve type safety in components

This commit is contained in:
Stevan Freeborn
2026-01-04 21:44:05 -06:00
parent 4f485644eb
commit 74b50f120b
7 changed files with 40 additions and 40 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ import PaymentForm from './PaymentForm';
import PaymentConfirmationCard from './PaymentConfirmationCard'; import PaymentConfirmationCard from './PaymentConfirmationCard';
import PreviousPaymentsList from './PreviousPaymentsList'; import PreviousPaymentsList from './PreviousPaymentsList';
const stripe = loadStripe(import.meta.env.VITE_STRIPE_API_KEY); const stripe = loadStripe(import.meta.env.VITE_STRIPE_API_KEY as string);
function App() { function App() {
const queryParams = new URLSearchParams(window.location.search); const queryParams = new URLSearchParams(window.location.search);
@@ -44,7 +44,7 @@ function App() {
return; return;
} }
const data = await res.json(); const data = await res.json() as { clientSecret: string };
setSecret(data.clientSecret); setSecret(data.clientSecret);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -32,7 +32,7 @@ export default function CheckoutForm() {
} }
return ( return (
<form onSubmit={handleSubmit}> <form onSubmit={e => void handleSubmit(e)}>
<PaymentElement <PaymentElement
id='payment-element' id='payment-element'
options={{ layout: 'accordion' }} options={{ layout: 'accordion' }}
@@ -58,7 +58,7 @@ export default function PaymentConfirmationCard({ clientSecret }: PaymentConfirm
} }
} }
fetchPaymentIntent(); void fetchPaymentIntent();
return () => { return () => {
isMounted = false; isMounted = false;
@@ -1,7 +1,7 @@
import { useState, useRef } from 'react'; import { useState, useRef } from 'react';
type DonationFormProps = { type DonationFormProps = {
onValidSubmit: (data: { name: string; amount: number; message?: string; email?: string }) => void; onValidSubmit: (data: { name: string; amount: number; message?: string; email?: string }) => Promise<void> | void;
isSubmitting?: boolean; isSubmitting?: boolean;
}; };
@@ -98,12 +98,12 @@ export default function PaymentForm({ onValidSubmit, isSubmitting }: DonationFor
throw new Error('amount after validation should never be an empty string'); throw new Error('amount after validation should never be an empty string');
} }
onValidSubmit({ name, amount, message, email }); await onValidSubmit({ name, amount, message, email });
} }
return ( return (
<form <form
onSubmit={handleSubmit} onSubmit={e => void handleSubmit(e)}
noValidate noValidate
> >
<div className='group'> <div className='group'>
@@ -45,7 +45,7 @@ export default function PreviousPaymentsList() {
setPaymentData({ status: 'error', message: 'Failed to fetch payments' }); setPaymentData({ status: 'error', message: 'Failed to fetch payments' });
} }
const data = await res.json(); const data = await res.json() as PaymentPage;
setPaymentData({ status: 'success', pages: [data] }); setPaymentData({ status: 'success', pages: [data] });
} catch (err) { } catch (err) {
@@ -58,7 +58,7 @@ export default function PreviousPaymentsList() {
} }
} }
fetchPayments(); void fetchPayments();
return () => { return () => {
isMounted = false; isMounted = false;
@@ -84,7 +84,7 @@ export default function PreviousPaymentsList() {
return; return;
} }
const data = await res.json(); const data = await res.json() as PaymentPage;
setPaymentData({ setPaymentData({
status: 'success', status: 'success',
@@ -117,7 +117,7 @@ export default function PreviousPaymentsList() {
<button <button
className='load-more-button' className='load-more-button'
type='button' type='button'
onClick={handleLoadMoreButtonClick} onClick={() => void handleLoadMoreButtonClick()}
disabled={currentPageNumber >= totalNumberOfPages || isLoadingMore} disabled={currentPageNumber >= totalNumberOfPages || isLoadingMore}
> >
Load More Load More
+3 -2
View File
@@ -1,7 +1,8 @@
@font-face { @font-face {
font-family: 'CaskaydiaCove NFM'; font-family: 'CaskaydiaCove NFM';
src: url('/CaskaydiaCoveNFM-Regular.eot'); src: url('/CaskaydiaCoveNFM-Regular.eot');
src: url('/CaskaydiaCoveNFM-Regular.eot?#iefix') format('embedded-opentype'), src:
url('/CaskaydiaCoveNFM-Regular.eot?#iefix') format('embedded-opentype'),
url('/CaskaydiaCoveNFM-Regular.woff2') format('woff2'), url('/CaskaydiaCoveNFM-Regular.woff2') format('woff2'),
url('/CaskaydiaCoveNFM-Regular.woff') format('woff'), url('/CaskaydiaCoveNFM-Regular.woff') format('woff'),
url('/CaskaydiaCoveNFM-Regular.svg#CaskaydiaCoveNFM-Regular') format('svg'); url('/CaskaydiaCoveNFM-Regular.svg#CaskaydiaCoveNFM-Regular') format('svg');
@@ -56,7 +57,7 @@ table {
:root { :root {
--bg-color: #323232; --bg-color: #323232;
--text-color: #E4E4E4; --text-color: #e4e4e4;
font-size: 16px; font-size: 16px;
font-family: 'CaskaydiaCove NFM', monospace; font-family: 'CaskaydiaCove NFM', monospace;
@@ -31,11 +31,11 @@ describe('form', () => {
user = userEvent.setup(); user = userEvent.setup();
vi.stubGlobal('fetch', mockFetch); vi.stubGlobal('fetch', mockFetch);
mockFetch.mockImplementation(async (url: URL) => { mockFetch.mockImplementation((url: URL) => {
if (url.toString().endsWith('/payments/create-intent')) { if (url.toString().endsWith('/payments/create-intent')) {
return { return {
ok: true, ok: true,
json: async () => ({ json: () => ({
clientSecret: 'test_client_secret', clientSecret: 'test_client_secret',
}), }),
}; };
@@ -44,7 +44,7 @@ describe('form', () => {
if (url.toString().endsWith('/payments')) { if (url.toString().endsWith('/payments')) {
return { return {
ok: true, ok: true,
json: async () => ({ json: () => ({
payments: [], payments: [],
}), }),
}; };
@@ -118,7 +118,6 @@ describe('form', () => {
await user.type(amountInput, '10'); await user.type(amountInput, '10');
await user.click(submitButton); await user.click(submitButton);
}); });
test('it should validate email if provided', async () => { test('it should validate email if provided', async () => {