Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | 1x 41x 41x 41x 23x 41x 23x 8x 41x 31x 1x 1x 1x 1x 41x 13x 13x 12x 13x 41x 1x 1x 41x 41x 41x 41x 41x 15x 26x | // 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) => {
Eif (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>
);
};
|