feat: add HeroUploadZone component for file uploads and ToolSelectorModal for tool selection

- Implemented HeroUploadZone for drag-and-drop file uploads with support for various file types (PDF, images, video, Word documents).
- Integrated ToolSelectorModal to display available tools based on the uploaded file type.
- Added Zustand store for managing file state across routes.
- Updated multiple tool components to accept files from the new upload zone.
- Enhanced internationalization support with new translations for upload prompts and tool selection.
- Styled the upload zone and modal for improved user experience.
This commit is contained in:
Your Name
2026-03-04 00:59:11 +02:00
parent aa80980a29
commit 2e97741d60
23 changed files with 692 additions and 17 deletions

View File

@@ -0,0 +1,149 @@
import { useState, useCallback } from 'react';
import { useDropzone } from 'react-dropzone';
import { useTranslation } from 'react-i18next';
import { Upload, Sparkles } from 'lucide-react';
import ToolSelectorModal from '@/components/shared/ToolSelectorModal';
import { getToolsForFile, detectFileCategory, getCategoryLabel } from '@/utils/fileRouting';
import type { ToolOption } from '@/utils/fileRouting';
/**
* The MIME types we accept on the homepage smart upload zone.
* Covers PDF, images, video, and Word documents.
*/
const ACCEPTED_TYPES = {
'application/pdf': ['.pdf'],
'image/jpeg': ['.jpg', '.jpeg'],
'image/png': ['.png'],
'image/webp': ['.webp'],
'video/mp4': ['.mp4'],
'video/webm': ['.webm'],
'application/msword': ['.doc'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
};
export default function HeroUploadZone() {
const { t } = useTranslation();
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [matchedTools, setMatchedTools] = useState<ToolOption[]>([]);
const [fileTypeLabel, setFileTypeLabel] = useState('');
const [modalOpen, setModalOpen] = useState(false);
const [error, setError] = useState<string | null>(null);
const onDrop = useCallback(
(acceptedFiles: File[]) => {
setError(null);
if (acceptedFiles.length === 0) return;
const file = acceptedFiles[0];
const tools = getToolsForFile(file);
if (tools.length === 0) {
setError(t('home.unsupportedFile'));
return;
}
const category = detectFileCategory(file);
const label = getCategoryLabel(category);
setSelectedFile(file);
setMatchedTools(tools);
setFileTypeLabel(label);
setModalOpen(true);
},
[t]
);
const { getRootProps, getInputProps, isDragActive } = useDropzone({
onDrop,
accept: ACCEPTED_TYPES,
maxFiles: 1,
maxSize: 100 * 1024 * 1024, // 100 MB (matches nginx config)
onDropRejected: (rejections) => {
const rejection = rejections[0];
if (rejection?.errors[0]?.code === 'file-too-large') {
setError(t('common.maxSize', { size: 100 }));
} else {
setError(t('home.unsupportedFile'));
}
},
});
const handleCloseModal = useCallback(() => {
setModalOpen(false);
setSelectedFile(null);
setMatchedTools([]);
}, []);
return (
<>
<div className="mx-auto mt-8 max-w-2xl">
<div
{...getRootProps()}
className={`hero-upload-zone ${isDragActive ? 'drag-active' : ''}`}
>
<input {...getInputProps()} />
{/* Icon */}
<div
className={`mb-4 flex h-16 w-16 items-center justify-center rounded-2xl transition-colors ${
isDragActive
? 'bg-primary-100 dark:bg-primary-900/30'
: 'bg-primary-50 dark:bg-primary-900/20'
}`}
>
<Upload
className={`h-8 w-8 transition-colors ${
isDragActive
? 'text-primary-600 dark:text-primary-400'
: 'text-primary-500 dark:text-primary-400'
}`}
/>
</div>
{/* CTA Text */}
<p className="mb-1 text-lg font-semibold text-slate-800 dark:text-slate-200">
{t('home.uploadCta')}
</p>
<p className="mb-3 text-sm text-slate-500 dark:text-slate-400">
{t('home.uploadOr')}
</p>
{/* Supported formats */}
<div className="flex flex-wrap items-center justify-center gap-2">
{['PDF', 'Word', 'JPG', 'PNG', 'WebP', 'MP4'].map((format) => (
<span
key={format}
className="rounded-full bg-slate-100 px-2.5 py-1 text-xs font-medium text-slate-600 dark:bg-slate-700 dark:text-slate-300"
>
{format}
</span>
))}
</div>
{/* File size hint */}
<p className="mt-3 flex items-center justify-center gap-1.5 text-xs text-slate-400 dark:text-slate-500">
<Sparkles className="h-3.5 w-3.5" />
{t('home.uploadSubtitle')}
</p>
</div>
{/* Error */}
{error && (
<div className="mt-3 rounded-xl bg-red-50 p-3 ring-1 ring-red-200 dark:bg-red-900/20 dark:ring-red-800">
<p className="text-center text-sm text-red-700 dark:text-red-400">{error}</p>
</div>
)}
</div>
{/* Tool Selector Modal */}
<ToolSelectorModal
isOpen={modalOpen}
onClose={handleCloseModal}
file={selectedFile}
tools={matchedTools}
fileTypeLabel={fileTypeLabel}
/>
</>
);
}

View File

@@ -0,0 +1,150 @@
import { useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { X, File as FileIcon } from 'lucide-react';
import { useFileStore } from '@/stores/fileStore';
import { formatFileSize } from '@/utils/textTools';
import type { ToolOption } from '@/utils/fileRouting';
interface ToolSelectorModalProps {
/** Whether the modal is open */
isOpen: boolean;
/** Callback to close the modal */
onClose: () => void;
/** The uploaded file */
file: File | null;
/** Available tools for this file type */
tools: ToolOption[];
/** Detected file type label (e.g. "PDF", "Image") */
fileTypeLabel: string;
}
export default function ToolSelectorModal({
isOpen,
onClose,
file,
tools,
fileTypeLabel,
}: ToolSelectorModalProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const setStoreFile = useFileStore((s) => s.setFile);
// Close on Escape key
useEffect(() => {
if (!isOpen) return;
function handleKeyDown(e: KeyboardEvent) {
if (e.key === 'Escape') onClose();
}
document.addEventListener('keydown', handleKeyDown);
return () => document.removeEventListener('keydown', handleKeyDown);
}, [isOpen, onClose]);
// Prevent body scroll when modal is open
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}
return () => {
document.body.style.overflow = '';
};
}, [isOpen]);
const handleToolSelect = useCallback(
(tool: ToolOption) => {
if (!file) return;
// Store file in zustand for the target tool to pick up
setStoreFile(file);
// Navigate to the tool page
navigate(tool.path);
onClose();
},
[file, setStoreFile, navigate, onClose]
);
// Close on backdrop click
const handleBackdropClick = useCallback(
(e: React.MouseEvent<HTMLDivElement>) => {
if (e.target === e.currentTarget) onClose();
},
[onClose]
);
if (!isOpen || !file) return null;
return (
<div
className="modal-backdrop fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4 backdrop-blur-sm"
onClick={handleBackdropClick}
role="dialog"
aria-modal="true"
aria-labelledby="tool-selector-title"
>
<div className="modal-content w-full max-w-lg rounded-2xl bg-white p-6 shadow-2xl ring-1 ring-slate-200 dark:bg-slate-800 dark:ring-slate-700">
{/* Header */}
<div className="mb-5 flex items-start justify-between">
<div>
<h2
id="tool-selector-title"
className="text-lg font-bold text-slate-900 dark:text-slate-100"
>
{t('home.selectTool')}
</h2>
<p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
{t('home.fileDetected', { type: fileTypeLabel })}
</p>
</div>
<button
onClick={onClose}
className="rounded-lg p-1.5 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600 dark:hover:bg-slate-700 dark:hover:text-slate-300"
aria-label="Close"
>
<X className="h-5 w-5" />
</button>
</div>
{/* File Info */}
<div className="mb-5 flex items-center gap-3 rounded-xl bg-primary-50 p-3 ring-1 ring-primary-200 dark:bg-primary-900/20 dark:ring-primary-800">
<FileIcon className="h-8 w-8 flex-shrink-0 text-primary-600 dark:text-primary-400" />
<div className="min-w-0 flex-1">
<p className="truncate text-sm font-medium text-slate-900 dark:text-slate-100">
{file.name}
</p>
<p className="text-xs text-slate-500 dark:text-slate-400">
{formatFileSize(file.size)}
</p>
</div>
</div>
{/* Tools Grid */}
<div className="grid grid-cols-2 gap-3 sm:grid-cols-3">
{tools.map((tool) => {
const Icon = tool.icon;
return (
<button
key={tool.key}
onClick={() => handleToolSelect(tool)}
className="group flex flex-col items-center gap-2 rounded-xl p-4 ring-1 ring-slate-200 transition-all hover:ring-primary-300 hover:shadow-md dark:ring-slate-700 dark:hover:ring-primary-600"
>
<div
className={`flex h-10 w-10 items-center justify-center rounded-xl ${tool.bgColor}`}
>
<Icon className={`h-5 w-5 ${tool.iconColor}`} />
</div>
<span className="text-center text-xs font-medium text-slate-700 group-hover:text-primary-600 dark:text-slate-300 dark:group-hover:text-primary-400">
{t(`tools.${tool.key}.shortDesc`)}
</span>
</button>
);
})}
</div>
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { ListOrdered } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type Position = 'bottom-center' | 'bottom-right' | 'bottom-left' | 'top-center' | 'top-right' | 'top-left';
@@ -40,6 +41,16 @@ export default function AddPageNumbers() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { ImageIcon } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type OutputFormat = 'jpg' | 'png' | 'webp';
@@ -40,6 +41,16 @@ export default function ImageConverter() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { FileImage } from 'lucide-react';
@@ -7,6 +7,7 @@ import ProgressBar from '@/components/shared/ProgressBar';
import DownloadButton from '@/components/shared/DownloadButton';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function ImagesToPdf() {
const { t } = useTranslation();
@@ -22,6 +23,16 @@ export default function ImagesToPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
setFiles((prev) => [...prev, storeFile]);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const acceptedTypes = ['image/png', 'image/jpeg', 'image/webp', 'image/bmp'];
const handleFilesSelect = (newFiles: FileList | File[]) => {

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Layers } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { uploadFile, type TaskResponse } from '@/services/api';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function MergePdf() {
const { t } = useTranslation();
@@ -25,6 +26,16 @@ export default function MergePdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
setFiles((prev) => [...prev, storeFile]);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleFilesSelect = (newFiles: FileList | File[]) => {
const fileArray = Array.from(newFiles).filter(
(f) => f.type === 'application/pdf'

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Minimize2 } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type Quality = 'low' | 'medium' | 'high';
@@ -39,6 +40,16 @@ export default function PdfCompressor() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { ImageIcon } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type OutputFormat = 'png' | 'jpg';
@@ -40,6 +41,16 @@ export default function PdfToImages() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { FileText } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function PdfToWord() {
const { t } = useTranslation();
@@ -35,6 +36,16 @@ export default function PdfToWord() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Lock } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function ProtectPdf() {
const { t } = useTranslation();
@@ -40,6 +41,16 @@ export default function ProtectPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
if (!passwordsMatch) return;
const id = await startUpload();

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { RotateCw } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type Rotation = 90 | 180 | 270;
@@ -39,6 +40,16 @@ export default function RotatePdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Scissors } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
type SplitMode = 'all' | 'range';
@@ -40,6 +41,16 @@ export default function SplitPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
if (mode === 'range' && !pages.trim()) return;
const id = await startUpload();

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Unlock } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function UnlockPdf() {
const { t } = useTranslation();
@@ -37,6 +38,16 @@ export default function UnlockPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
if (!password) return;
const id = await startUpload();

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Film } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function VideoToGif() {
const { t } = useTranslation();
@@ -45,6 +46,16 @@ export default function VideoToGif() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { Droplets } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function WatermarkPdf() {
const { t } = useTranslation();
@@ -38,6 +39,16 @@ export default function WatermarkPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useEffect } from 'react';
import { useTranslation } from 'react-i18next';
import { Helmet } from 'react-helmet-async';
import { FileOutput } from 'lucide-react';
@@ -9,6 +9,7 @@ import AdSlot from '@/components/layout/AdSlot';
import { useFileUpload } from '@/hooks/useFileUpload';
import { useTaskPolling } from '@/hooks/useTaskPolling';
import { generateToolSchema } from '@/utils/seo';
import { useFileStore } from '@/stores/fileStore';
export default function WordToPdf() {
const { t } = useTranslation();
@@ -35,6 +36,16 @@ export default function WordToPdf() {
onError: () => setPhase('done'),
});
// Accept file from homepage smart upload
const storeFile = useFileStore((s) => s.file);
const clearStoreFile = useFileStore((s) => s.clearFile);
useEffect(() => {
if (storeFile) {
selectFile(storeFile);
clearStoreFile();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
const handleUpload = async () => {
const id = await startUpload();
if (id) setPhase('processing');