Files
flyer-crawler.projectium.com/src/features/flyer/FlyerUploader.tsx
Torben Sorensen 17fac8cf86
All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 13m1s
flyer upload (anon) issues
2025-12-30 20:44:34 -08:00

162 lines
5.5 KiB
TypeScript

// src/features/flyer/FlyerUploader.tsx
import React, { useEffect, useCallback } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { logger } from '../../services/logger.client';
import { ProcessingStatus } from './ProcessingStatus';
import { useDragAndDrop } from '../../hooks/useDragAndDrop';
import { useFlyerUploader } from '../../hooks/useFlyerUploader';
interface FlyerUploaderProps {
onProcessingComplete: () => void;
}
export const FlyerUploader: React.FC<FlyerUploaderProps> = ({ onProcessingComplete }) => {
const navigate = useNavigate();
const {
processingState,
statusMessage,
errorMessage,
duplicateFlyerId,
processingStages,
estimatedTime,
currentFile,
flyerId,
upload,
resetUploaderState,
} = useFlyerUploader();
useEffect(() => {
if (statusMessage) logger.info(`FlyerUploader Status: ${statusMessage}`);
}, [statusMessage]);
useEffect(() => {
if (errorMessage) {
logger.error(`[FlyerUploader] Error encountered: ${errorMessage}`, { duplicateFlyerId });
}
}, [errorMessage, duplicateFlyerId]);
// Handle completion and navigation
useEffect(() => {
if (processingState === 'completed' && flyerId) {
onProcessingComplete();
// Small delay to show the "Complete" state before redirecting
const timer = setTimeout(() => {
navigate(`/flyers/${flyerId}`);
}, 1500);
return () => clearTimeout(timer);
}
}, [processingState, flyerId, onProcessingComplete, navigate]);
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (file) {
upload(file);
}
event.target.value = '';
};
const onFilesDropped = useCallback(
(files: FileList) => {
if (files && files.length > 0) {
upload(files[0]);
}
},
[upload],
);
const isProcessing = processingState === 'uploading' || processingState === 'polling';
const { isDragging, dropzoneProps } = useDragAndDrop<HTMLLabelElement>({
onFilesDropped,
disabled: isProcessing,
});
const borderColor = isDragging ? 'border-brand-primary' : 'border-gray-400 dark:border-gray-600';
const bgColor = isDragging
? 'bg-brand-light/50 dark:bg-brand-dark/20'
: 'bg-gray-50/50 dark:bg-gray-800/20';
if (isProcessing || processingState === 'completed' || processingState === 'error') {
return (
<div className="max-w-4xl mx-auto">
<ProcessingStatus
stages={processingStages}
estimatedTime={estimatedTime}
currentFile={currentFile}
/>
<div className="mt-4 text-center">
{/* Display status message if not completed (completed has its own redirect logic) */}
{statusMessage && processingState !== 'completed' && (
<p className="text-gray-600 dark:text-gray-400 mt-2 italic animate-pulse">
{statusMessage}
</p>
)}
{processingState === 'completed' && (
<p className="text-green-600 dark:text-green-400 mt-2 font-bold">
Processing complete! Redirecting to flyer {flyerId}...
</p>
)}
{errorMessage && (
<div className="text-red-600 dark:text-red-400 font-semibold p-4 bg-red-100 dark:bg-red-900/30 rounded-md">
{duplicateFlyerId ? (
<p>
{errorMessage} You can view it here:{' '}
<Link to={`/flyers/${duplicateFlyerId}`} className="text-blue-500 underline" data-discover="true">
Flyer #{duplicateFlyerId}
</Link>
</p>
) : (
<p>{errorMessage}</p>
)}
</div>
)}
{processingState === 'polling' && (
<button
onClick={resetUploaderState}
className="mt-4 text-sm text-gray-500 hover:text-gray-800 dark:hover:text-gray-200 underline transition-colors"
title="The flyer will continue to process in the background."
>
Stop Watching Progress
</button>
)}
{(processingState === 'error' || processingState === 'completed') && (
<button
onClick={resetUploaderState}
className="mt-4 text-sm bg-brand-secondary hover:bg-brand-dark text-white font-bold py-2 px-4 rounded-lg"
>
Upload Another Flyer
</button>
)}
</div>
</div>
);
}
return (
<div className="max-w-xl mx-auto p-6 bg-white dark:bg-gray-800 rounded-lg shadow-md">
<h2 className="text-2xl font-bold mb-4 text-center">Upload New Flyer</h2>
<div className="flex flex-col items-center space-y-4">
<label
htmlFor="flyer-upload"
className={`w-full text-center px-4 py-6 border-2 border-dashed rounded-lg cursor-pointer transition-colors duration-200 ease-in-out ${bgColor} ${borderColor} hover:border-brand-primary/70 dark:hover:border-brand-primary/50`}
{...dropzoneProps}
>
<span className="text-lg font-medium">Click to select a file</span>
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
or drag and drop a PDF or image
</p>
<input
id="flyer-upload"
type="file"
className="hidden"
onChange={handleFileChange}
accept=".pdf,image/*"
/>
</label>
</div>
</div>
);
};