diff --git a/dev-dist/sw.js b/dev-dist/sw.js index f5c2847..c48f875 100644 --- a/dev-dist/sw.js +++ b/dev-dist/sw.js @@ -82,7 +82,7 @@ define(['./workbox-5a5d9309'], (function (workbox) { 'use strict'; "revision": "3ca0b8505b4bec776b69afdba2768812" }, { "url": "index.html", - "revision": "0.sskc1m612v8" + "revision": "0.9sdrrlu31pk" }], {}); workbox.cleanupOutdatedCaches(); workbox.registerRoute(new workbox.NavigationRoute(workbox.createHandlerBoundToURL("index.html"), { diff --git a/package-lock.json b/package-lock.json index 649948f..7b6b075 100644 --- a/package-lock.json +++ b/package-lock.json @@ -112,6 +112,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", @@ -1597,7 +1598,8 @@ "version": "0.3.15", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz", "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@electric-sql/pglite-tools": { "version": "0.2.20", @@ -2244,6 +2246,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.8.tgz", "integrity": "sha512-WiE9uCGRLUnShdjb9iP20sA3ToWrBbNXr14/N5mow7Nls9dmKgfGaGX5cynLvrltxq2OrDLh1VDNaUgsnS/k/g==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/component": "0.7.0", "@firebase/logger": "0.5.0", @@ -2310,6 +2313,7 @@ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.8.tgz", "integrity": "sha512-4De6SUZ36zozl9kh5rZSxKWULpgty27rMzZ6x+xkoo7+NWyhWyFdsdvhFsWhTw/9GGj0wXIcbTjwHYCUIUuHyg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@firebase/app": "0.14.8", "@firebase/component": "0.7.0", @@ -2325,7 +2329,8 @@ "version": "0.9.3", "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz", "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==", - "license": "Apache-2.0" + "license": "Apache-2.0", + "peer": true }, "node_modules/@firebase/auth": { "version": "1.12.0", @@ -2776,6 +2781,7 @@ "integrity": "sha512-0AZUyYUfpMNcztR5l09izHwXkZpghLgCUaAGjtMwXnCg3bj4ml5VgiwqOMOxJ+Nw4qN/zJAaOQBcJ7KGkWStqQ==", "hasInstallScript": true, "license": "Apache-2.0", + "peer": true, "dependencies": { "tslib": "^2.1.0" }, @@ -3996,6 +4002,7 @@ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", "license": "Apache-2.0", + "peer": true, "engines": { "node": ">=8.0.0" } @@ -4645,6 +4652,7 @@ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", @@ -4701,6 +4709,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.2.3.tgz", "integrity": "sha512-m0jEgYlYz+mDJZ2+F4v8D1AyQb+QzsNqRuI7xg1VQX/KlKS0qT9r1Mo16yo5F/MtifXFgaofIFsdFMox2SxIbQ==", "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -4711,6 +4720,7 @@ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -4804,6 +4814,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5781,6 +5792,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -7568,6 +7580,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8135,6 +8148,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -9784,6 +9798,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -9893,6 +9908,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.28.4" }, @@ -11261,6 +11277,7 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-13.0.3.tgz", "integrity": "sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA==", "license": "MIT", + "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -12383,6 +12400,7 @@ "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", + "peer": true, "dependencies": { "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", @@ -12906,6 +12924,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12915,6 +12934,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -13481,6 +13501,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "license": "MIT", + "peer": true, "dependencies": { "@types/estree": "1.0.8" }, @@ -15269,6 +15290,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -15796,6 +15818,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "license": "MIT", + "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -15848,6 +15871,7 @@ "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", "license": "MIT", + "peer": true, "bin": { "rollup": "dist/bin/rollup" }, @@ -16258,6 +16282,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src/components/ui/FileUploader.css b/src/components/ui/FileUploader.css index 6ff172b..c8e0d11 100644 --- a/src/components/ui/FileUploader.css +++ b/src/components/ui/FileUploader.css @@ -209,3 +209,15 @@ margin-bottom: 20px; } } +.upload-error { + margin-bottom: 12px; + padding: 10px; + border-radius: 6px; + background: #ffe6e6; + color: #b30000; + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; +} + diff --git a/src/components/ui/FileUploader.jsx b/src/components/ui/FileUploader.jsx index c23e598..60584c6 100644 --- a/src/components/ui/FileUploader.jsx +++ b/src/components/ui/FileUploader.jsx @@ -5,12 +5,94 @@ import imageService from '../../services/imageService'; import './FileUploader.css'; function FileUploader({ tool, customLayout = false }) { + const [errorMessage, setErrorMessage] = useState(''); const [files, setFiles] = useState([]); const [isDragging, setIsDragging] = useState(false); const [processing, setProcessing] = useState(false); const [processedResults, setProcessedResults] = useState([]); const fileInputRef = useRef(null); + /* ---------------- FILE SIZE FORMATTER (Moved Up — must exist before use) ---------------- */ + const formatFileSize = (bytes) => { + if (bytes === 0) return '0 Bytes'; + const k = 1024; + const sizes = ['Bytes', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; + }; + + /* ---------------- VALIDATION ---------------- */ + + const isValidPDF = (file) => file.type === 'application/pdf'; + const isValidImage = (file) => file.type.startsWith('image/'); + + const validateFileForTool = (file) => { + if (tool.id.startsWith('pdf')) return isValidPDF(file); + if (tool.id.startsWith('image')) return isValidImage(file); + return true; + }; + + /* ---------------- FILE HANDLING ---------------- */ + + const addFiles = (newFiles) => { + const valid = []; + const invalid = []; + const duplicates = []; + + newFiles.forEach((file) => { + + // 🔍 Check if file already exists + const alreadyExists = files.some( + (f) => + f.file.name === file.name && + f.file.size === file.size && + f.file.lastModified === file.lastModified + ); + + if (alreadyExists) { + duplicates.push(file.name); + return; + } + + if (validateFileForTool(file)) { + valid.push({ + id: Math.random().toString(36).substr(2, 9), + file, + name: file.name, + size: formatFileSize(file.size), + status: 'ready' + }); + } else { + invalid.push(file.name); + } + }); + + // Show proper messages + if (duplicates.length > 0) { + setErrorMessage(`Duplicate file ignored: ${duplicates.join(', ')}`); + } + else if (invalid.length > 0) { + setErrorMessage(`Invalid file type for ${tool.name}: ${invalid.join(', ')}`); + } + else { + setErrorMessage(''); + } + + if (valid.length > 0) { + setFiles((prev) => [...prev, ...valid]); + setProcessedResults([]); + } +}; + + + const removeFile = (id) => { + setFiles((prev) => prev.filter((f) => f.id !== id)); + setProcessedResults([]); + setErrorMessage(''); + }; + + /* ---------------- DRAG EVENTS ---------------- */ + const handleDragOver = (e) => { e.preventDefault(); setIsDragging(true); @@ -24,8 +106,7 @@ function FileUploader({ tool, customLayout = false }) { const handleDrop = (e) => { e.preventDefault(); setIsDragging(false); - const droppedFiles = Array.from(e.dataTransfer.files); - addFiles(droppedFiles); + addFiles(Array.from(e.dataTransfer.files)); }; const handleFileSelect = (e) => { @@ -33,30 +114,7 @@ function FileUploader({ tool, customLayout = false }) { addFiles(selectedFiles); }; - const addFiles = (newFiles) => { - const fileObjects = newFiles.map((file) => ({ - id: Math.random().toString(36).substr(2, 9), - file, - name: file.name, - size: formatFileSize(file.size), - status: 'ready' - })); - setFiles((prev) => [...prev, ...fileObjects]); - setProcessedResults([]); - }; - - const removeFile = (id) => { - setFiles((prev) => prev.filter((f) => f.id !== id)); - setProcessedResults([]); - }; - - const formatFileSize = (bytes) => { - if (bytes === 0) return '0 Bytes'; - const k = 1024; - const sizes = ['Bytes', 'KB', 'MB', 'GB']; - const i = Math.floor(Math.log(bytes) / Math.log(k)); - return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; - }; + /* ---------------- PROCESSING ---------------- */ const processFiles = async () => { setProcessing(true); @@ -67,32 +125,47 @@ function FileUploader({ tool, customLayout = false }) { setProcessedResults([result]); setFiles(prev => prev.map(f => ({ ...f, status: 'completed' }))); } + else if (tool.id === 'pdf-split') { const file = files[0].file; const results = await pdfService.splitPDF(file); setProcessedResults(results); setFiles(prev => prev.map(f => ({ ...f, status: 'completed' }))); } - else if (tool.id === 'image-convert' || tool.id === 'image-compress') { + + else if (tool.id.startsWith('image')) { const results = []; + for (let i = 0; i < files.length; i++) { - setFiles(prev => prev.map((f, idx) => idx === i ? { ...f, status: 'processing' } : f)); + setFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, status: 'processing' } : f + )); + const format = tool.id === 'image-convert' ? 'image/png' : 'image/jpeg'; const quality = tool.id === 'image-compress' ? 0.6 : 0.9; + const result = await imageService.convertImage(files[i].file, format, quality); + results.push({ blob: result, originalName: files[i].name, format }); - setFiles(prev => prev.map((f, idx) => idx === i ? { ...f, status: 'completed' } : f)); + + setFiles(prev => prev.map((f, idx) => + idx === i ? { ...f, status: 'completed' } : f + )); } + setProcessedResults(results); } } catch (error) { console.error('Processing error:', error); setFiles(prev => prev.map(f => ({ ...f, status: 'error' }))); + setErrorMessage('Processing failed. Please try again.'); } finally { setProcessing(false); } }; + /* ---------------- DOWNLOAD ---------------- */ + const handleDownload = () => { if (processedResults.length === 0) return; if (tool.id === 'pdf-merge') { @@ -101,45 +174,20 @@ function FileUploader({ tool, customLayout = false }) { processedResults.forEach((data, index) => pdfService.downloadPDF(data, `page_${index + 1}.pdf`)); } else if (tool.id.startsWith('image-')) { processedResults.forEach((res) => { - const ext = res.format.split('/')[1]; - imageService.downloadBlob(res.blob, `${res.originalName.split('.')[0]}_forge.${ext}`); + const ext = String(res.format || '') + .replace('image/', '') + .trim() || 'png'; + const lastDotIndex = res.originalName.lastIndexOf('.'); + const baseName = + lastDotIndex !== -1 + ? res.originalName.substring(0, lastDotIndex) + : res.originalName; + imageService.downloadBlob(res.blob, `${baseName}_forge.${ext}`); }); } }; - if (files.length === 0 && customLayout) { - return ( -
or drop PDFs here
- -Support for PDF, Images, and Documents