diff --git a/app/tool/[id]/processing/page.tsx b/app/tool/[id]/processing/page.tsx index 33dce37..dcc3236 100644 --- a/app/tool/[id]/processing/page.tsx +++ b/app/tool/[id]/processing/page.tsx @@ -5,7 +5,8 @@ import { useEffect, useState } from "react"; import { useParams, useRouter } from "next/navigation"; import Tesseract from "tesseract.js"; import { getStoredFiles, clearStoredFiles } from "@/lib/fileStore"; -import { PDFDocument } from "pdf-lib"; +import { PDFDocument, rgb, degrees } from "pdf-lib"; +import { useRecentFiles } from "@/lib/hooks/useRecentFiles"; type StoredFile = { data: string; @@ -17,6 +18,7 @@ export default function ProcessingPage() { const router = useRouter(); const params = useParams(); const toolId = params.id as string; + const { addRecentFile } = useRecentFiles(); const [status, setStatus] = useState<"processing" | "done" | "error">( "processing" @@ -26,6 +28,7 @@ export default function ProcessingPage() { const [error, setError] = useState(""); const [copied, setCopied] = useState(false); const [downloadUrl, setDownloadUrl] = useState(null); + const [processedFile, setProcessedFile] = useState(null); /* ================= RUN TOOL ================= */ useEffect(() => { @@ -37,22 +40,28 @@ export default function ProcessingPage() { return; } + setProcessedFile(stored[0]); // Keep reference for recent files + try { - if (toolId === "ocr") await runOCR(stored[0].data); + if (toolId === "ocr") await runOCR(stored[0]); else if (toolId === "pdf-protect") - await protectPDF(stored[0].data); + await protectPDF(stored[0]); else if (toolId === "jpeg-to-pdf") - await imageToPdf(stored[0].data, "jpg"); + await imageToPdf(stored[0], "jpg"); else if (toolId === "png-to-pdf") - await imageToPdf(stored[0].data, "png"); + await imageToPdf(stored[0], "png"); else if (toolId === "pdf-compress") await startCompressFlow(stored); - else setStatus("done"); + else { + // Default success case + addToRecent(stored[0].name); + setStatus("done"); + } } catch (e) { console.error(e); setError("Processing failed"); @@ -65,9 +74,17 @@ export default function ProcessingPage() { run(); }, [toolId, router]); + const addToRecent = (fileName: string) => { + addRecentFile({ + fileName, + tool: toolId, + time: new Date().toLocaleString(), + }); + }; + /* ================= OCR ================= */ - const runOCR = async (base64: string) => { - const res = await Tesseract.recognize(base64, "eng", { + const runOCR = async (file: StoredFile) => { + const res = await Tesseract.recognize(file.data, "eng", { logger: (m) => { if (m.status === "recognizing text") { setProgress(Math.round(m.progress * 100)); @@ -76,6 +93,7 @@ export default function ProcessingPage() { }); setText(res.data.text); + addToRecent(file.name); setStatus("done"); }; @@ -110,23 +128,27 @@ export default function ProcessingPage() { ); setDownloadUrl(makeBlobUrl(bytes)); + addToRecent(files[0].name); // Add the first file to recent setProgress(100); setStatus("done"); }; + + /* ================= PDF PROTECT ================= */ - const protectPDF = async (base64: string) => { - const bytes = base64ToBytes(base64); + const protectPDF = async (file: StoredFile) => { + const bytes = base64ToBytes(file.data); const pdf = await PDFDocument.load(bytes); const saved = await pdf.save(); setDownloadUrl(makeBlobUrl(saved)); + addToRecent(file.name); setStatus("done"); }; /* ================= IMAGE → PDF ================= */ - const imageToPdf = async (base64: string, type: "jpg" | "png") => { - const bytes = base64ToBytes(base64); + const imageToPdf = async (file: StoredFile, type: "jpg" | "png") => { + const bytes = base64ToBytes(file.data); const pdf = await PDFDocument.create(); const img = @@ -145,6 +167,7 @@ export default function ProcessingPage() { const saved = await pdf.save(); setDownloadUrl(makeBlobUrl(saved)); + addToRecent(file.name); setStatus("done"); }; @@ -207,8 +230,8 @@ export default function ProcessingPage() { {toolId === "jpeg-to-pdf" ? "JPEG Converted to PDF!" : toolId === "png-to-pdf" - ? "PNG Converted to PDF!" - : "Completed Successfully"} + ? "PNG Converted to PDF!" + : "Completed Successfully"} {downloadUrl && ( diff --git a/components/ConfirmationModal.tsx b/components/ConfirmationModal.tsx new file mode 100644 index 0000000..391e101 --- /dev/null +++ b/components/ConfirmationModal.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { motion, AnimatePresence } from "framer-motion"; +import { AlertTriangle, X } from "lucide-react"; +import { useEffect } from "react"; +import { cn } from "@/lib/utils"; // Assuming utils exists, if not I'll inline, but usually it does in these setups. Checking first might be safer but `clsx` and `tailwind-merge` are in package.json so valid. +// I'll check if lib/utils exists first to be safe, or just use clsx/twMerge directly. +// Actually, looking at previous file reads, `lib/hooks/useRecentFiles.ts` exists. +// I'll assume standard `lib/utils` for now, if it fails I'll fix. +// Wait, I haven't seen `lib/utils.ts`. I should probably check or just write safe code. +// I'll write safe code using clsx and tailwind-merge directly to avoid assumption failures. + +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +interface ConfirmationModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + title: string; + message: string; + confirmText?: string; + cancelText?: string; + type?: "danger" | "neutral"; +} + +export function ConfirmationModal({ + isOpen, + onClose, + onConfirm, + title, + message, + confirmText = "Confirm", + cancelText = "Cancel", + type = "danger", +}: ConfirmationModalProps) { + // Close on Escape key + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + if (isOpen) document.addEventListener("keydown", handleEscape); + return () => document.removeEventListener("keydown", handleEscape); + }, [isOpen, onClose]); + + return ( + + {isOpen && ( +
+ {/* Backdrop */} + + + {/* Modal */} + +
+
+
+ +
+
+

+ {title} +

+

+ {message} +

+
+
+ +
+ + +
+
+ + +
+
+ )} +
+ ); +} diff --git a/components/RecentFiles.tsx b/components/RecentFiles.tsx index ec49a8c..9522b4c 100644 --- a/components/RecentFiles.tsx +++ b/components/RecentFiles.tsx @@ -1,6 +1,6 @@ "use client"; -import { Trash2 } from "lucide-react"; +import { Trash2, FileText as FileIcon } from "lucide-react"; export type RecentFile = { fileName: string; @@ -19,13 +19,12 @@ export default function RecentFiles({ files, onDelete, onClear }: RecentFilesPro return (
- {/* Header with Clear Button */} -
-

Recent Files

+
+

Recent Files

@@ -35,23 +34,32 @@ export default function RecentFiles({ files, onDelete, onClear }: RecentFilesPro {files.map((file, index) => (
-
-

{file.fileName}

-

- {file.tool} • {file.time} -

+
+
+ +
+
+

{file.fileName}

+
+ + {file.tool.replace("-", " ")} + + {file.time} +
+
- {/* Delete Button */} - +
+ +
))}
diff --git a/components/RecentlyDeletedFiles.tsx b/components/RecentlyDeletedFiles.tsx index 27697fe..91930c0 100644 --- a/components/RecentlyDeletedFiles.tsx +++ b/components/RecentlyDeletedFiles.tsx @@ -1,7 +1,7 @@ -"use client"; - -import { Trash2, RotateCcw } from "lucide-react"; +import { useState } from "react"; +import { Trash2, RotateCcw, FileText as FileIcon } from "lucide-react"; import { DeletedFile } from "@/lib/hooks/useRecentFiles"; +import { ConfirmationModal } from "./ConfirmationModal"; interface RecentlyDeletedFilesProps { deletedFiles: DeletedFile[]; @@ -16,16 +16,35 @@ export default function RecentlyDeletedFiles({ onPermanentDelete, onClear, }: RecentlyDeletedFilesProps) { + const [confirmAction, setConfirmAction] = useState<{ + type: "delete" | "clear"; + index?: number; + } | null>(null); + if (deletedFiles.length === 0) return null; + const handleConfirm = () => { + if (!confirmAction) return; + + if (confirmAction.type === "delete" && confirmAction.index !== undefined) { + onPermanentDelete(confirmAction.index); + } else if (confirmAction.type === "clear") { + onClear(); + } + setConfirmAction(null); + }; + return (
-
-

Recently Deleted

+
+

+ + Recently Deleted +

@@ -35,29 +54,34 @@ export default function RecentlyDeletedFiles({ {deletedFiles.map((file, index) => (
-
-

{file.fileName}

-

- {file.tool} • {file.time} -

-

- Deleted: {file.deletedTime} -

+
+
+ +
+
+

{file.fileName}

+
+ + {file.tool.replace("-", " ")} + + Deleted: {file.deletedTime} +
+
-
+
))}
+ + setConfirmAction(null)} + onConfirm={handleConfirm} + title={ + confirmAction?.type === "delete" + ? "Delete File Permanently?" + : "Clear Deletion History?" + } + message={ + confirmAction?.type === "delete" + ? "This action cannot be undone. The file will be permanently removed from your history." + : "This will permanently remove all files from your deletion history. This action cannot be undone." + } + confirmText="Delete" + cancelText="Cancel" + />
); }