// src/App.tsx import React, { useState, useCallback, useEffect } from 'react'; import { Routes, Route, useParams } from 'react-router-dom'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import * as pdfjsLib from 'pdfjs-dist'; import { Footer } from './components/Footer'; import { Header } from './components/Header'; import { logger } from './services/logger.client'; import type { Flyer, Profile, UserProfile } from './types'; import { ProfileManager } from './pages/admin/components/ProfileManager'; import { VoiceAssistant } from './features/voice-assistant/VoiceAssistant'; import { AdminPage } from './pages/admin/AdminPage'; import { AdminRoute } from './components/AdminRoute'; import { CorrectionsPage } from './pages/admin/CorrectionsPage'; import { AdminStatsPage } from './pages/admin/AdminStatsPage'; import { ResetPasswordPage } from './pages/ResetPasswordPage'; import { VoiceLabPage } from './pages/VoiceLabPage'; import { FlyerCorrectionTool } from './components/FlyerCorrectionTool'; import { QuestionMarkCircleIcon } from './components/icons/QuestionMarkCircleIcon'; import { useAuth } from './hooks/useAuth'; import { useFlyers } from './hooks/useFlyers'; import { useFlyerItems } from './hooks/useFlyerItems'; import { useModal } from './hooks/useModal'; import { MainLayout } from './layouts/MainLayout'; import config from './config'; import { HomePage } from './pages/HomePage'; import { AppGuard } from './components/AppGuard'; import { useAppInitialization } from './hooks/useAppInitialization'; // pdf.js worker configuration // This is crucial for allowing pdf.js to process PDFs in a separate thread, preventing the UI from freezing. // We need to explicitly tell pdf.js where to load its worker script from. // By importing pdfjs-dist, we can host the worker locally, which is more reliable. pdfjsLib.GlobalWorkerOptions.workerSrc = new URL( 'pdfjs-dist/build/pdf.worker.mjs', import.meta.url, ).toString(); // Create a client const queryClient = new QueryClient(); function App() { const { userProfile, authStatus, login, logout, updateProfile } = useAuth(); const { flyers } = useFlyers(); const [selectedFlyer, setSelectedFlyer] = useState(null); const { openModal, closeModal, isModalOpen } = useModal(); const params = useParams<{ flyerId?: string }>(); // This hook now handles initialization effects (OAuth, version check, theme) // and returns the theme/unit state needed by other components. const { isDarkMode, unitSystem } = useAppInitialization(); // Debugging: Log renders to identify infinite loops useEffect(() => { if (process.env.NODE_ENV === 'test') { console.log('[App] Render:', { flyersCount: flyers.length, selectedFlyerId: selectedFlyer?.flyer_id, paramsFlyerId: params?.flyerId, // This was a duplicate, fixed. authStatus, profileId: userProfile?.user.user_id, }); } }); const { flyerItems } = useFlyerItems(selectedFlyer); // Define modal handlers with useCallback at the top level to avoid Rules of Hooks violations const handleOpenProfile = useCallback(() => openModal('profile'), [openModal]); const handleCloseProfile = useCallback(() => closeModal('profile'), [closeModal]); const handleOpenVoiceAssistant = useCallback(() => openModal('voiceAssistant'), [openModal]); const handleCloseVoiceAssistant = useCallback(() => closeModal('voiceAssistant'), [closeModal]); const handleOpenWhatsNew = useCallback(() => openModal('whatsNew'), [openModal]); const handleCloseWhatsNew = useCallback(() => closeModal('whatsNew'), [closeModal]); const handleOpenCorrectionTool = useCallback(() => openModal('correctionTool'), [openModal]); const handleCloseCorrectionTool = useCallback(() => closeModal('correctionTool'), [closeModal]); const handleDataExtractedFromCorrection = useCallback( (type: 'store_name' | 'dates', value: string) => { if (!selectedFlyer) return; // This is a simplified update. A real implementation would involve // making another API call to update the flyer record in the database. // For now, we just update the local state for immediate visual feedback. const updatedFlyer = { ...selectedFlyer }; if (type === 'store_name') { updatedFlyer.store = { ...updatedFlyer.store!, name: value }; } else if (type === 'dates') { // A more robust solution would parse the date string properly. } setSelectedFlyer(updatedFlyer); }, [selectedFlyer], ); const handleProfileUpdate = useCallback( (updatedProfileData: Profile) => { // When the profile is updated, the API returns a `Profile` object. // We need to merge it with the existing `user` object to maintain // the `UserProfile` type in our state. updateProfile(updatedProfileData); }, [updateProfile], ); // --- State Synchronization and Error Handling --- // This is the login handler that will be passed to the ProfileManager component. const handleLoginSuccess = useCallback( async (userProfile: UserProfile, token: string, _rememberMe: boolean) => { try { await login(token, userProfile); // After successful login, fetch user-specific data // The useData hook will automatically refetch user data when `user` changes. // We can remove the explicit fetch here. } catch (e) { // The `login` function within the `useAuth` hook already handles its own errors // and notifications, so we just need to log any unexpected failures here. logger.error({ err: e }, 'An error occurred during the login success handling.'); } }, [login], ); const handleFlyerSelect = useCallback(async (flyer: Flyer) => { setSelectedFlyer(flyer); }, []); useEffect(() => { if (!selectedFlyer && flyers.length > 0) { if (process.env.NODE_ENV === 'test') console.log('[App] Effect: Auto-selecting first flyer'); handleFlyerSelect(flyers[0]); } }, [flyers, selectedFlyer, handleFlyerSelect]); // New effect to handle routing to a specific flyer ID from the URL useEffect(() => { const flyerIdFromUrl = params.flyerId; if (flyerIdFromUrl && flyers.length > 0) { const flyerId = parseInt(flyerIdFromUrl, 10); const flyerToSelect = flyers.find((f) => f.flyer_id === flyerId); if (flyerToSelect && flyerToSelect.flyer_id !== selectedFlyer?.flyer_id) { handleFlyerSelect(flyerToSelect); } } }, [flyers, handleFlyerSelect, selectedFlyer, params.flyerId]); // Read the application version injected at build time. // This will only be available in the production build, not during local development. const appVersion = config.app.version; return ( // AppGuard now handles the main page wrapper, theme styles, and "What's New" modal
{userProfile && ( )} {selectedFlyer && ( )} } > } /> } /> {/* Admin Routes */} }> } /> } /> } /> } /> } /> {/* Add other top-level routes here if needed */} {appVersion && ( )}