pdfs / frontend /src /components /FileConverter.tsx
ketannnn's picture
New start
2cacf4c
Raw
History Blame Contribute Delete
8.4 kB
"use client";
import { useState, useRef } from "react";
import axios from "axios";
import { useSession } from "next-auth/react";
interface FileConverterProps {
accessToken: string;
}
export default function FileConverter({ accessToken }: FileConverterProps) {
const [files, setFiles] = useState<File[]>([]);
const [isUploading, setIsUploading] = useState(false);
const [progress, setProgress] = useState(0);
const [outputName, setOutputName] = useState("converted_document");
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
if (e.target.files) {
setFiles(prev => [...prev, ...Array.from(e.target.files!)]);
}
};
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
if (e.dataTransfer.files) {
setFiles(prev => [...prev, ...Array.from(e.dataTransfer.files)]);
}
};
const removeFile = (index: number) => {
setFiles(prev => prev.filter((_, i) => i !== index));
};
const handleUpload = async () => {
if (files.length === 0) return;
setIsUploading(true);
setProgress(10);
const formData = new FormData();
formData.append("token", accessToken);
formData.append("filename", outputName);
files.forEach((file) => {
formData.append("files", file);
});
try {
setProgress(30);
const response = await axios.post("/py-api/convert-local-files", formData, {
responseType: "blob",
params: { combine: false }, // Explicitly separate
onUploadProgress: (progressEvent) => {
const percentCompleted = Math.round((progressEvent.loaded * 100) / (progressEvent.total || 1));
setProgress(30 + (percentCompleted * 0.6));
}
});
setProgress(100);
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement("a");
link.href = url;
link.setAttribute("download", `${outputName}.zip`);
document.body.appendChild(link);
link.click();
link.remove();
setTimeout(() => {
setIsUploading(false);
setProgress(0);
}, 2000);
} catch (error) {
console.error("Conversion failed:", error);
alert("Conversion failed. Please try again.");
setIsUploading(false);
setProgress(0);
}
};
return (
<div className="w-full max-w-3xl bg-white/80 backdrop-blur-md rounded-2xl shadow-xl border border-white/20 p-8 transition-all duration-300 hover:shadow-2xl">
<h2 className="text-2xl font-bold text-gray-800 mb-6 flex items-center gap-2">
<span className="bg-blue-500 text-white p-2 rounded-lg">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" />
</svg>
</span>
Quick Converter
</h2>
<div
className="border-2 border-dashed border-blue-200 rounded-xl p-10 flex flex-col items-center justify-center bg-blue-50/30 hover:bg-blue-50/50 transition-colors cursor-pointer"
onClick={() => fileInputRef.current?.click()}
onDragOver={(e) => e.preventDefault()}
onDrop={handleDrop}
>
<input
type="file"
multiple
hidden
ref={fileInputRef}
onChange={handleFileChange}
accept=".docx,.pptx,.ipynb"
/>
<div className="w-16 h-16 bg-blue-100 text-blue-600 rounded-full flex items-center justify-center mb-4">
<svg xmlns="http://www.w3.org/2000/svg" className="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" />
</svg>
</div>
<p className="text-gray-600 font-medium">Click to upload or drag & drop</p>
<p className="text-gray-400 text-sm mt-1">Supports DOCX, PPTX, IPYNB</p>
</div>
{files.length > 0 && (
<div className="mt-8 space-y-4">
<div className="flex justify-between items-center">
<h3 className="font-semibold text-gray-700">Selected Files ({files.length})</h3>
<button
onClick={() => setFiles([])}
className="text-sm text-red-500 hover:underline"
>
Clear All
</button>
</div>
<div className="max-h-60 overflow-y-auto space-y-2 pr-2">
{files.map((file, index) => (
<div key={index} className="flex items-center justify-between bg-gray-50 p-3 rounded-lg border border-gray-100 group animate-in fade-in slide-in-from-left-2 duration-300">
<div className="flex items-center gap-3">
<div className="w-8 h-8 flex items-center justify-center bg-white rounded shadow-sm">
{file.name.endsWith('.docx') && <span className="text-blue-500 text-xs font-bold">DOC</span>}
{file.name.endsWith('.pptx') && <span className="text-orange-500 text-xs font-bold">PPT</span>}
{file.name.endsWith('.ipynb') && <span className="text-yellow-600 text-xs font-bold">NB</span>}
</div>
<span className="text-sm text-gray-700 truncate max-w-[200px] md:max-w-md">{file.name}</span>
</div>
<button
onClick={(e) => { e.stopPropagation(); removeFile(index); }}
className="text-gray-400 hover:text-red-500 p-1 rounded-full hover:bg-red-50 transition-colors"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fillRule="evenodd" d="M4.293 4.293a1 1 0 011.414 0L10 8.586l4.293-4.293a1 1 0 111.414 1.414L11.414 10l4.293 4.293a1 1 0 01-1.414 1.414L10 11.414l-4.293 4.293a1 1 0 01-1.414-1.414L8.586 10 4.293 5.707a1 1 0 010-1.414z" clipRule="evenodd" />
</svg>
</button>
</div>
))}
</div>
<div className="pt-4 border-t border-gray-100 space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Output PDF Name</label>
<input
type="text"
value={outputName}
onChange={(e) => setOutputName(e.target.value)}
className="w-full px-4 py-2 bg-gray-50 border border-gray-200 rounded-lg focus:ring-2 focus:ring-blue-500 outline-none transition-all"
placeholder="Name your file..."
/>
</div>
<button
onClick={handleUpload}
disabled={isUploading || files.length === 0}
className={`w-full py-3 rounded-xl font-bold text-white transition-all transform active:scale-95 flex items-center justify-center gap-2 ${
isUploading ? 'bg-blue-400 cursor-not-allowed' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700 shadow-lg hover:shadow-blue-200'
}`}
>
{isUploading ? (
<>
<svg className="animate-spin h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Processing... {Math.round(progress)}%
</>
) : (
<>
Convert to Separate PDFs (ZIP)
</>
)}
</button>
</div>
</div>
)}
{isUploading && (
<div className="mt-4 w-full bg-gray-100 h-2 rounded-full overflow-hidden">
<div
className="bg-blue-500 h-full transition-all duration-300"
style={{ width: `${progress}%` }}
/>
</div>
)}
</div>
);
}