diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 87e803d..05f9794 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -86,6 +86,9 @@ importers: react-dom: specifier: 19.2.7 version: 19.2.7(react@19.2.7) + xlsx: + specifier: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz + version: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz devDependencies: '@types/react': specifier: ^19.2.0 @@ -1759,6 +1762,12 @@ packages: utf-8-validate: optional: true + xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: + resolution: {integrity: sha512-oLDq3jw7AcLqKWH2AhCpVTZl8mf6X2YReP+Neh0SJUzV/BdZYjth94tG5toiMB1PPrYtxOCfaoUCkvtuH+3AJA==, tarball: https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz} + version: 0.20.3 + engines: {node: '>=0.8'} + hasBin: true + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} @@ -3353,6 +3362,8 @@ snapshots: ws@8.21.0: {} + xlsx@https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz: {} + yallist@3.1.1: {} yocto-queue@0.1.0: {} diff --git a/web/index.html b/web/index.html index 07e24dd..404887e 100644 --- a/web/index.html +++ b/web/index.html @@ -14,12 +14,18 @@ content="The whole pipeline in your browser: scrub, rinse, soak, steep. Cleaning runs locally; the AI phases are bring-your-own-key." /> - + + + + + + + diff --git a/web/package.json b/web/package.json index 6396f1d..9efa864 100644 --- a/web/package.json +++ b/web/package.json @@ -12,7 +12,8 @@ "@anthropic-ai/sdk": "^0.39.0", "datasink": "workspace:*", "react": "^19.2.0", - "react-dom": "^19.2.0" + "react-dom": "^19.2.0", + "xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz" }, "devDependencies": { "@types/react": "^19.2.0", diff --git a/web/public/og.png b/web/public/og.png new file mode 100644 index 0000000..e94218c Binary files /dev/null and b/web/public/og.png differ diff --git a/web/src/components/DropZone.tsx b/web/src/components/DropZone.tsx index c8a387e..e3ca1ab 100644 --- a/web/src/components/DropZone.tsx +++ b/web/src/components/DropZone.tsx @@ -17,6 +17,7 @@ export function DropZone({ const [urlValue, setUrlValue] = useState('') const [spotValue, setSpotValue] = useState('') const [urlError, setUrlError] = useState(null) + const [fileError, setFileError] = useState(null) const [fetching, setFetching] = useState(false) const fileInput = useRef(null) @@ -31,9 +32,26 @@ export function DropZone({ const readFile = useCallback( (file: File) => { + setFileError(null) const reader = new FileReader() - reader.onload = () => submitCsv(String(reader.result ?? '')) - reader.readAsText(file) + if (/\.xlsx?$/i.test(file.name)) { + // Excel — parse the first sheet to CSV, then run the normal pipeline. + reader.onload = async () => { + try { + const XLSX = await import('xlsx') + const wb = XLSX.read(reader.result, { type: 'array' }) + const sheet = wb.Sheets[wb.SheetNames[0]] + if (!sheet) throw new Error('empty workbook') + submitCsv(XLSX.utils.sheet_to_csv(sheet)) + } catch { + setFileError('Couldn’t read that spreadsheet — try exporting it as CSV.') + } + } + reader.readAsArrayBuffer(file) + } else { + reader.onload = () => submitCsv(String(reader.result ?? '')) + reader.readAsText(file) + } }, [submitCsv], ) @@ -99,7 +117,7 @@ export function DropZone({ { const file = e.target.files?.[0] @@ -108,9 +126,10 @@ export function DropZone({ /> ◇ - Drop your contacts CSV here + Drop your contacts CSV or Excel file click to browse, or paste straight from your spreadsheet {error && {error}} + {fileError && {fileError}} e.stopPropagation()}> submitCsv('__DEMO__')}> diff --git a/web/src/styles/global.css b/web/src/styles/global.css index cca41ee..8abc691 100644 --- a/web/src/styles/global.css +++ b/web/src/styles/global.css @@ -28,7 +28,8 @@ html { body { font-family: var(--mono); color: var(--text); - background: linear-gradient(160deg, #06b6d4 0%, #22d3ee 35%, #a5f3fc 70%, #ecfeff 100%); + background: linear-gradient(165deg, #06b6d4 0%, #22d3ee 40%, #67e8f9 72%, #a5f3fc 100%); + background-attachment: fixed; min-height: 100vh; } diff --git a/web/vercel.json b/web/vercel.json index 65b2762..c054f89 100644 --- a/web/vercel.json +++ b/web/vercel.json @@ -1,5 +1,5 @@ { - "buildCommand": "pnpm build", + "buildCommand": "cd .. && pnpm build && pnpm --filter sink-web build", "outputDirectory": "dist", "installCommand": "cd .. && pnpm install --frozen-lockfile", "framework": "vite"
Drop your contacts CSV here
Drop your contacts CSV or Excel file
click to browse, or paste straight from your spreadsheet
{error}
{fileError}