fix(import): guard unparseable CSV dates and never wedge the button (L7)
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]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
8274af7d5c
commit
9bd21b185d
@@ -0,0 +1,46 @@
|
||||
// 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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user