refactor: standardise antd imports and remove the avoidable any (#65)
SonarQube Analysis / sonarqube (pull_request) Canceled after 0s
Tests / lint (pull_request) Canceled after 0s
Tests / backend-unit (pull_request) Canceled after 0s
Tests / frontend-e2e (pull_request) Canceled after 0s

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>
This commit is contained in:
2026-08-21 15:48:12 -05:00
co-authored by Claude Opus 5
parent 567e8ba650
commit a700597440
12 changed files with 130 additions and 23 deletions
+4 -3
View File
@@ -1,4 +1,5 @@
import { Router, Request, Response } from 'express';
import type { PoolClient } from 'pg';
import { pool } from '../db';
import { asyncRoute } from '../asyncRoute';
import { requireCustomer } from '../middleware/customerAuth';
@@ -38,7 +39,7 @@ interface LockedCart {
// Locks the customer's cart, verifies every item is still reserved to them,
// and returns { cartId, items: [{id, name, price_cents}], totalCents }.
async function loadLockedCart(client: any, customerId: number): Promise<LockedCart | null> {
async function loadLockedCart(client: PoolClient, customerId: number): Promise<LockedCart | null> {
const { rows: cartRows } = await client.query(`SELECT id FROM carts WHERE customer_id = $1`, [customerId]);
if (!cartRows.length) return null;
const cartId = cartRows[0].id;
@@ -64,7 +65,7 @@ type OpenedCheckout =
// items. The caller owns the transaction — on `ok: false` it should roll back
// and return the error as a 400.
async function openCheckout(
client: any,
client: PoolClient,
customerId: number,
shippingAddressId: number,
processor: string,
@@ -145,7 +146,7 @@ router.post('/paypal/create', requireCustomer, asyncRoute(async (req: Request, r
// Returns the sold item ids and the buyer, so the caller can notify favoriters
// *after* COMMIT. Sending inside the transaction would email people about a
// sale that then rolled back, and would hold the transaction open for SMTP.
async function completeCheckout(client: any, checkoutId: number, processor: string, processorOrderId: string | null, rawEvent: unknown): Promise<{ itemIds: number[]; buyerId: number | null }> {
async function completeCheckout(client: PoolClient, checkoutId: number, processor: string, processorOrderId: string | null, rawEvent: unknown): Promise<{ itemIds: number[]; buyerId: number | null }> {
const { rows: checkoutItems } = await client.query(
`SELECT item_id, price_cents FROM checkout_items WHERE checkout_id = $1`,
[checkoutId]
+15 -1
View File
@@ -32,7 +32,21 @@ async function createSession(customerId: number): Promise<string> {
return token;
}
function publicCustomer(c: any) {
// The subset of a customers row that is safe to return to the customer it
// belongs to. Typed as its own shape rather than `any` so that adding a column
// to the table — a password hash, a token, an internal note — cannot silently
// start being echoed back by a `...c` somewhere downstream.
interface CustomerRow {
id: number;
email: string;
name: string | null;
email_verified: boolean;
marketing_consent: boolean;
favorite_alerts: boolean;
created_at: Date;
}
function publicCustomer(c: CustomerRow) {
return {
id: c.id,
email: c.email,