Preview the contacts CSV before import
React · React · beginner · greenfield
Adds the pre-import preview for the contacts CSV upload. Picking a file reads it in the browser, parses the rows (stripping the exporter's quoting, including doubled inner quotes), and renders the three-column preview table, with an empty-state message for header-only files. Re-picking the same file re-runs the preview thanks to the input reset.
Requirements
- Add the `ContactImportPreview` component: the user picks a `.csv` file of contacts and the component renders a preview table (Name / Email / Company) of every row so they can eyeball the data before confirming the import.
- The CSV format: UTF-8, no BOM, first line is always the header `name,email,company`, `\n` or `\r\n` line endings. A field is wrapped in double quotes when (and only when) it contains a comma or a double quote; a double quote inside a quoted field is doubled (`""`). Field values never contain line breaks.
- Contact names and company names routinely contain commas in real exports (`"Smith, John"`, `"Acme, Inc."`) — the preview must show every field in its correct column for such rows.
- Blank lines (including a trailing newline) are skipped. If the file has no data rows, show `No contacts found in this file.`
- Reset the file input's value after reading so re-picking the same file re-runs the preview. Reading a just-picked file via `file.text()` may be treated as infallible — no error state is required for it.
Files touched
- src/components/ContactImportPreview.jsx
--- src/components/ContactImportPreview.jsx
+import { useState } from 'react';
+
+function unquote(field) {
+ if (field.startsWith('"') && field.endsWith('"')) {
+ return field.slice(1, -1).replace(/""/g, '"');
+ }
+ return field;
+}
+
+export function parseContacts(text) {
+ const lines = text.split(/\r?\n/).filter((line) => line.trim().length > 0);
+ return lines.slice(1).map((line) => {
+ const cells = line.split(',').map(unquote);
+ return { name: cells[0], email: cells[1], company: cells[2] };
+ });
+}
+
+export default function ContactImportPreview() {
+ const [rows, setRows] = useState(null);
+
+ async function handleFile(event) {
+ const file = event.target.files[0];
+ if (!file) return;
+ const text = await file.text();
+ setRows(parseContacts(text));
+ event.target.value = '';
+ }
+
+ return (