import React from 'react'; import { YStack, XStack } from '@tamagui/stacks'; import { SizableText as Text } from '@tamagui/text'; import { Button } from '@tamagui/button'; import { RefreshCcw, Trash2, UploadCloud } from 'lucide-react'; import AppShell from '../components/AppShell'; import SurfaceCard from '../components/SurfaceCard'; import { useUploadQueue } from '../services/uploadApi'; import { useTranslation } from '@/guest/i18n/useTranslation'; import { useGuestThemeVariant } from '../lib/guestTheme'; import { useLocale } from '@/guest/i18n/LocaleContext'; import { useEventData } from '../context/EventDataContext'; import { fetchPendingUploadsSummary, type PendingUpload } from '@/guest/services/pendingUploadsApi'; type ProgressMap = Record; export default function UploadQueueScreen() { const { t } = useTranslation(); const { locale } = useLocale(); const { token } = useEventData(); const { items, loading, retryAll, clearFinished, refresh, remove } = useUploadQueue(); const [progress, setProgress] = React.useState({}); const { isDark } = useGuestThemeVariant(); const mutedText = isDark ? 'rgba(248, 250, 252, 0.7)' : 'rgba(15, 23, 42, 0.65)'; const [pending, setPending] = React.useState([]); const [pendingLoading, setPendingLoading] = React.useState(false); const [pendingError, setPendingError] = React.useState(null); const formatter = React.useMemo( () => new Intl.DateTimeFormat(locale, { day: '2-digit', month: 'short', hour: '2-digit', minute: '2-digit' }), [locale] ); const formatTimestamp = React.useCallback( (value?: string | null) => { if (!value) { return t('pendingUploads.card.justNow', 'Just now'); } const date = new Date(value); if (Number.isNaN(date.getTime())) { return t('pendingUploads.card.justNow', 'Just now'); } return formatter.format(date); }, [formatter, t] ); const loadPendingUploads = React.useCallback(async () => { if (!token) { setPending([]); return; } try { setPendingLoading(true); setPendingError(null); const result = await fetchPendingUploadsSummary(token, 12); setPending(result.items); } catch (err) { console.error('Pending uploads load failed', err); setPendingError(t('pendingUploads.error', 'Failed to load uploads. Please try again.')); } finally { setPendingLoading(false); } }, [t, token]); React.useEffect(() => { void loadPendingUploads(); }, [loadPendingUploads]); React.useEffect(() => { const handler = (event: Event) => { const detail = (event as CustomEvent<{ id?: number; progress?: number }>).detail; if (!detail?.id || typeof detail.progress !== 'number') return; setProgress((prev) => ({ ...prev, [detail.id!]: detail.progress! })); }; window.addEventListener('queue-progress', handler as EventListener); return () => window.removeEventListener('queue-progress', handler as EventListener); }, []); const activeCount = items.filter((item) => item.status !== 'done').length; const failedCount = items.filter((item) => item.status === 'error').length; return ( {t('uploadQueue.title', 'Uploads')} {t('uploadQueue.description', 'Keep track of queued uploads and retries.')} {t( 'uploadQueue.summary', { waiting: activeCount, failed: failedCount }, '{waiting} waiting · {failed} failed' )} {loading ? ( {t('common.actions.loading', 'Loading...')} ) : items.length === 0 ? ( {t('uploadQueue.emptyTitle', 'No queued uploads')} {t('uploadQueue.emptyDescription', 'Once photos are queued, they will appear here.')} ) : ( items .slice() .sort((a, b) => (b.createdAt ?? 0) - (a.createdAt ?? 0)) .map((item) => { const pct = typeof item.id === 'number' ? progress[item.id] : undefined; return ( {item.fileName} {item.status === 'done' ? t('uploadQueue.status.uploaded', 'Uploaded') : item.status === 'uploading' ? t('uploadQueue.status.uploading', 'Uploading') : item.status === 'error' ? t('uploadQueue.status.failed', 'Failed') : t('uploadQueue.status.waiting', 'Waiting')} {item.retries ? t('uploadQueue.status.retries', { count: item.retries }, ' · {count} retries') : ''} {pct !== undefined ? t('uploadQueue.progress', { progress: pct }, '{progress}%') : item.status === 'done' ? t('uploadQueue.progress', { progress: 100 }, '{progress}%') : ''} {item.status === 'error' && typeof item.id === 'number' ? ( ) : null} ); }) )} {t('pendingUploads.title', 'Pending uploads')} {t('pendingUploads.subtitle', 'Your photos are waiting for approval.')} {pendingLoading ? ( {t('pendingUploads.loading', 'Loading uploads...')} ) : pendingError ? ( {pendingError} ) : pending.length === 0 ? ( {t('pendingUploads.emptyTitle', 'No pending uploads')} {t('pendingUploads.emptyBody', 'Once you upload a photo, it will appear here until it is approved.')} ) : ( pending.map((photo) => ( {photo.thumbnail_url ? ( ) : ( )} {t('pendingUploads.card.pending', 'Waiting for approval')} {t('pendingUploads.card.uploadedAt', 'Uploaded {time}').replace('{time}', formatTimestamp(photo.created_at))} )) )} ); }