Stage 1 of #65: this issue's original two lists. The type-checked gate it also owns follows in later stages. Nine files imported antd from the barrel while the rest of the codebase used deep imports from antd/es. Both resolve to the same modules under antd v5 and Vite, so this is not the tree-shaking problem it would have been under v4 — the cost was that a documented convention had two spellings, and nobody reading a file could tell whether its style was deliberate or just old. Eighty-two imports converted, and every antd/es path was checked to exist before generating any of them rather than trusting a name-mangling rule. The three `client: any` parameters in cartCheckout are now PoolClient. These functions run inside a transaction, and `any` removed exactly the check that would catch a pool-versus-client mix-up — which in this codebase means a query silently running outside the transaction it was meant to be part of, on the path that takes money. publicCustomer took `any` and now takes a CustomerRow describing what it actually reads. Typed as its own shape rather than the whole table so that adding a column later — a password hash, a token, an internal note — cannot quietly start being echoed back to a customer. The three `(window as any).paypal` casts are replaced by a declared interface for the injected SDK. It is deliberately narrow: it describes the three things this app calls, not the whole SDK, because a wider guess would be fiction and a wrong shape typed confidently is worse than an honest cast. The property is optional, since the SDK is absent until its script has loaded — which is the check both call sites already make. Verified: 141 unit, 169 integration and 94 end-to-end passing. The end-to-end run is the one that matters here — an antd import migration can build cleanly and still break at runtime through styles or context, so a green tsc proves less than it appears to. Lint drops from 8 warnings to 4 in the backend and 30 to 27 in the frontend, all of them the no-explicit-any this change removed. As a side effect the no-unsafe count that later stages exist to clear falls from 259 to 216 in the backend and 73 to 67 in the frontend, measured rather than estimated. Refs #65 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
41 lines
1.6 KiB
TypeScript
Executable File
41 lines
1.6 KiB
TypeScript
Executable File
// The PayPal JS SDK is injected at runtime by the script tag below, so there is
|
|
// no package to import types from. This declares the sliver of it this app
|
|
// actually uses, rather than repeating `(window as any).paypal` at each call
|
|
// site — three casts that each silently opted out of type checking.
|
|
//
|
|
// Deliberately narrow: it describes what is called here, not the whole SDK. A
|
|
// wider guess would be fiction, and a wrong shape typed confidently is worse
|
|
// than an honest `unknown`.
|
|
export interface PaypalButtonsConfig {
|
|
createOrder: () => Promise<string>;
|
|
onApprove: (data: { orderID: string }) => Promise<void> | void;
|
|
onError: (err: unknown) => void;
|
|
}
|
|
|
|
export interface PaypalSdk {
|
|
Buttons: (config: PaypalButtonsConfig) => { render: (selector: string) => void };
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
// Absent until the SDK script has loaded, which is what loadPaypalSdk and
|
|
// every caller has to check before using it.
|
|
paypal?: PaypalSdk;
|
|
}
|
|
}
|
|
|
|
let loadPromise: Promise<void> | null = null;
|
|
|
|
export function loadPaypalSdk(clientId: string, currency: string): Promise<void> {
|
|
if (window.paypal) return Promise.resolve();
|
|
if (loadPromise) return loadPromise;
|
|
loadPromise = new Promise((resolve, reject) => {
|
|
const script = document.createElement('script');
|
|
script.src = `https://www.paypal.com/sdk/js?client-id=${encodeURIComponent(clientId)}¤cy=${currency}`;
|
|
script.onload = () => resolve();
|
|
script.onerror = () => reject(new Error('failed to load paypal sdk'));
|
|
document.head.appendChild(script);
|
|
});
|
|
return loadPromise;
|
|
}
|