A non-empty invalid transaction_date ('N/A') passed presence-only validation,
then new Date(str).toISOString() threw mid-import: nothing imported, no toast,
and setImporting(false) never ran, sticking the button on 'Importing...'.
Extracted parsePaymentRow to lib/import-logic.ts with a date guard, and wrapped
runImport so the importing flag always clears.
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
47 lines
1.8 KiB
TypeScript
47 lines
1.8 KiB
TypeScript
// Pure parse/validate for one column-mapped CSV payment row, shared by the
|
|
// import wizard's payments path. Extracted so the date guard is unit-testable:
|
|
// a non-empty but unparseable transaction_date (e.g. 'N/A') must surface as a
|
|
// per-row error, never throw. A thrown RangeError from `new Date(str).toISOString()`
|
|
// mid-map aborts the entire import — nothing gets sent, no toast fires, and the
|
|
// wizard's 'Importing...' button stays wedged. Validation here mirrors the
|
|
// server's Zod row schema loosely (presence + a real date); the server remains
|
|
// the source of truth and re-validates every row.
|
|
|
|
export interface ParsedPaymentRow {
|
|
platform: string
|
|
amount: number
|
|
from_identifier: string
|
|
from_note: string | undefined
|
|
transaction_date: string
|
|
}
|
|
|
|
export type PaymentRowResult =
|
|
| { ok: true; value: ParsedPaymentRow }
|
|
| { ok: false; error: string }
|
|
|
|
// `mapped` is the app-field-keyed row (field key -> raw cell string). `rowNumber`
|
|
// is the 1-based spreadsheet row (header = row 1) used only for error messages.
|
|
export function parsePaymentRow(
|
|
mapped: Record<string, string>,
|
|
rowNumber: number,
|
|
): PaymentRowResult {
|
|
const amount = parseFloat((mapped.amount ?? '').replace(/[$,\s]/g, ''))
|
|
if (isNaN(amount) || !mapped.from_identifier || !mapped.transaction_date) {
|
|
return { ok: false, error: `Row ${rowNumber}: missing required fields` }
|
|
}
|
|
const parsed = new Date(mapped.transaction_date)
|
|
if (isNaN(parsed.getTime())) {
|
|
return { ok: false, error: `Row ${rowNumber}: unparseable date "${mapped.transaction_date}"` }
|
|
}
|
|
return {
|
|
ok: true,
|
|
value: {
|
|
platform: mapped.platform || 'csv',
|
|
amount,
|
|
from_identifier: mapped.from_identifier,
|
|
from_note: mapped.from_note,
|
|
transaction_date: parsed.toISOString(),
|
|
},
|
|
}
|
|
}
|