Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 124 additions & 10 deletions pages/utilities/csv-to-json.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useState, useRef } from "react";
import { Textarea } from "@/components/ds/TextareaComponent";
import PageHeader from "@/components/PageHeader";
import { Card } from "@/components/ds/CardComponent";
Expand All @@ -12,12 +12,16 @@ import CallToActionGrid from "@/components/CallToActionGrid";
import CsvToJsonSEO from "@/components/seo/CsvToJsonSEO";
import Meta from "@/components/Meta";
import { convertCSVtoJSON } from "@/components/utils/csv-to-json.utils";
import { cn } from "@/lib/utils";
import { UploadIcon } from "lucide-react";

export default function CSVtoJSON() {
const [input, setInput] = useState("");
const [output, setOutput] = useState("");
const { buttonText, handleCopy } = useCopyToClipboard();
const [lowercase, setLowercase] = useState(false);
const [isDragging, setIsDragging] = useState(false);
const fileInputRef = useRef<HTMLInputElement>(null);

const handleChange = useCallback(
(event: React.ChangeEvent<HTMLTextAreaElement>) => {
Expand Down Expand Up @@ -59,11 +63,82 @@ export default function CSVtoJSON() {
});
}, [input]);

const processFile = useCallback(
(file: File) => {
if (!file.type.startsWith("text/") && !file.name.endsWith(".csv")) {
setOutput("Please upload a CSV file");
return;
}

const reader = new FileReader();
reader.onload = (e) => {
const content = e.target?.result as string;
setInput(content);

try {
const json = convertCSVtoJSON(content, lowercase);
setOutput(json);
} catch (errorMessage: unknown) {
setOutput(errorMessage as string);
}
};
reader.readAsText(file);
},
[lowercase]
);

const handleDragOver = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(true);
}, []);

const handleDragLeave = useCallback((e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);
}, []);

const handlePaste = useCallback(
(e: React.ClipboardEvent<HTMLTextAreaElement>) => {
const file = e.clipboardData.files?.[0];
if (file) {
processFile(file);
}
},
[processFile]
);

const handleDrop = useCallback(
(e: React.DragEvent<HTMLDivElement>) => {
e.preventDefault();
setIsDragging(false);

const file = e.dataTransfer.files?.[0];
if (file) {
processFile(file);
}
},
[processFile]
);

const handleFileInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
processFile(file);
}
},
[processFile]
);

const triggerFileInput = useCallback(() => {
fileInputRef.current?.click();
}, []);

return (
<main>
<Meta
title="CSV to JSON Converter | Free, Open Source & Ad-free"
description="Convert CSV files to JSON format quickly and easily with Jam's free online CSV to JSON converter. Just paste your CSV file and get the JSON result. That's it."
description="Convert CSV files to JSON format quickly and easily with Jam's free online CSV to JSON converter. Upload your CSV file or paste its content and get the JSON result. That's it."
/>
<Header />
<CMDK />
Expand All @@ -78,14 +153,53 @@ export default function CSVtoJSON() {
<section className="container max-w-2xl mb-6">
<Card className="flex flex-col p-6 hover:shadow-none shadow-none rounded-xl">
<div>
<Label>CSV</Label>
<Textarea
rows={6}
placeholder="Paste CSV here"
onChange={handleChange}
className="mb-6"
value={input}
/>
<div className="flex justify-between items-center mb-2">
<Label className="mb-0">CSV</Label>
<Button
variant="outline"
onClick={triggerFileInput}
type="button"
size="sm"
className="gap-2"
>
<UploadIcon className="w-[16px]" /> Upload CSV
</Button>
<input
ref={fileInputRef}
type="file"
accept=".csv"
onChange={handleFileInputChange}
className="hidden"
/>
</div>

<div
className="relative"
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
>
<Textarea
rows={6}
placeholder="Paste or drag and drop a CSV file"
onInput={handleChange}
onPaste={handlePaste}
className={cn("mb-6", {
"border-dashed": isDragging,
"bg-white": isDragging,
"border-gray-300": isDragging,
})}
value={input}
/>

{isDragging && (
<div className="absolute inset-0 flex items-center justify-center pointer-events-none">
<p className="text-sm bg-white p-2 rounded-md shadow">
Drop it like it's hot 🔥
</p>
</div>
)}
</div>

<div className="flex justify-between items-center mb-2">
<Label className="mb-0">JSON</Label>
Expand Down