import { useState, useEffect, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { Helmet } from 'react-helmet-async'; import { LucideIcon, AlertCircle, CheckCircle, Clock } from 'lucide-react'; import FileUploader from '@/components/shared/FileUploader'; import CostEstimatePanel from '@/components/shared/CostEstimatePanel'; import ProgressBar from '@/components/shared/ProgressBar'; import DownloadButton from '@/components/shared/DownloadButton'; 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 interface ToolConfig { slug: string; icon: LucideIcon; color?: 'orange' | 'red' | 'blue' | 'green' | 'purple' | 'pink' | 'amber' | 'cyan'; i18nKey: string; endpoint: string; maxSizeMB?: number; acceptedTypes?: string[]; isPremium?: boolean; extraData?: Record; } export interface ToolTemplateProps { file: File | null; uploadProgress: number; isUploading: boolean; isProcessing: boolean; result: any; error: string | null; selectFile: (file: File) => void; reset: () => void; } const colorMap: Record = { orange: { bg: 'bg-orange-50 dark:bg-orange-900/20', icon: 'text-orange-600 dark:text-orange-400' }, red: { bg: 'bg-red-50 dark:bg-red-900/20', icon: 'text-red-600 dark:text-red-400' }, blue: { bg: 'bg-blue-50 dark:bg-blue-900/20', icon: 'text-blue-600 dark:text-blue-400' }, green: { bg: 'bg-green-50 dark:bg-green-900/20', icon: 'text-green-600 dark:text-green-400' }, purple: { bg: 'bg-purple-50 dark:bg-purple-900/20', icon: 'text-purple-600 dark:text-purple-400' }, pink: { bg: 'bg-pink-50 dark:bg-pink-900/20', icon: 'text-pink-600 dark:text-pink-400' }, amber: { bg: 'bg-amber-50 dark:bg-amber-900/20', icon: 'text-amber-600 dark:text-amber-400' }, cyan: { bg: 'bg-cyan-50 dark:bg-cyan-900/20', icon: 'text-cyan-600 dark:text-cyan-400' }, }; interface ToolTemplateComponentProps { config: ToolConfig; onGetExtraData?: () => Record; children?: (props: ToolTemplateProps) => React.ReactNode; } export default function ToolTemplate({ config, onGetExtraData, children }: ToolTemplateComponentProps) { const { t } = useTranslation(); const [phase, setPhase] = useState<'upload' | 'processing' | 'done'>('upload'); const [extraData, setExtraData] = useState>(config.extraData || {}); const colors = colorMap[config.color || 'blue']; const bgColor = colors.bg; const iconColor = colors.icon; const { file, uploadProgress, isUploading, taskId, error, selectFile, startUpload, reset } = useFileUpload({ endpoint: config.endpoint, maxSizeMB: config.maxSizeMB || 20, acceptedTypes: config.acceptedTypes || ['pdf'], extraData, }); const { status, result } = useTaskPolling({ taskId, onComplete: () => setPhase('done'), onError: () => setPhase('done'), }); const storeFile = useFileStore((s) => s.file); const clearStoreFile = useFileStore((s) => s.clearFile); useEffect(() => { if (storeFile && config.acceptedTypes?.some((type) => storeFile.name.endsWith(`.${type}`))) { selectFile(storeFile); clearStoreFile(); setPhase('upload'); } }, []); const handleUpload = useCallback(async () => { // Get fresh extraData from child if callback provided if (onGetExtraData) { const freshExtraData = onGetExtraData(); setExtraData(freshExtraData); } const id = await startUpload(); if (id) setPhase('processing'); }, [onGetExtraData, startUpload]); const handleReset = () => { reset(); setPhase('upload'); }; const title = t(`${config.i18nKey}.title`, { defaultValue: config.slug }); const description = t(`${config.i18nKey}.description`, { defaultValue: '' }); const schema = generateToolSchema({ name: title, description, url: `${window.location.origin}/tools/${config.slug}`, }); const templateProps: ToolTemplateProps = { file, uploadProgress, isUploading, isProcessing: phase === 'processing', result, error, selectFile, reset: handleReset, }; return ( <> {title} — Dociva

{title}

{description}

{phase === 'upload' && (
{ selectFile(f); setPhase('upload'); }} file={file} accept={config.acceptedTypes?.reduce( (acc, type) => ({ ...acc, [`application/${type}`]: [`.${type}`], }), {} ) || {}} /> {children && (
{children(templateProps)}
)}
)} {phase === 'processing' && (
)} {phase === 'done' && (
{result ? ( <>

{t('result.success')}

{t('result.fileReady')}

) : (

{t('common.error')}

{error || t('common.errors.processingFailed')}

)}
)}
); }