All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m20s
173 lines
5.8 KiB
TypeScript
173 lines
5.8 KiB
TypeScript
// src/layouts/MainLayout.tsx
|
|
import React, { useCallback } from 'react';
|
|
import { Outlet } from 'react-router-dom';
|
|
import { useAuth } from '../hooks/useAuth';
|
|
import { useOnboardingTour } from '../hooks/useOnboardingTour';
|
|
import { useFlyers } from '../hooks/useFlyers';
|
|
import { useShoppingLists } from '../hooks/useShoppingLists';
|
|
import { useMasterItems } from '../hooks/useMasterItems';
|
|
import { useWatchedItems } from '../hooks/useWatchedItems';
|
|
import { useActiveDeals } from '../hooks/useActiveDeals';
|
|
|
|
import { FlyerList } from '../features/flyer/FlyerList';
|
|
import { FlyerUploader } from '../features/flyer/FlyerUploader';
|
|
import { ShoppingListComponent } from '../features/shopping/ShoppingList';
|
|
import { WatchedItemsList } from '../features/shopping/WatchedItemsList';
|
|
import { PriceChart } from '../features/charts/PriceChart';
|
|
import { PriceHistoryChart } from '../features/charts/PriceHistoryChart';
|
|
import Leaderboard from '../components/Leaderboard';
|
|
import { ActivityLog, ActivityLogClickHandler } from '../pages/admin/ActivityLog';
|
|
import { AnonymousUserBanner } from '../components/AnonymousUserBanner';
|
|
import { ErrorDisplay } from '../components/ErrorDisplay';
|
|
|
|
export interface MainLayoutProps {
|
|
onFlyerSelect: (flyer: import('../types').Flyer) => void;
|
|
selectedFlyerId: number | null;
|
|
onOpenProfile: () => void;
|
|
}
|
|
|
|
export const MainLayout: React.FC<MainLayoutProps> = ({
|
|
onFlyerSelect,
|
|
selectedFlyerId,
|
|
onOpenProfile,
|
|
}) => {
|
|
const { userProfile, authStatus } = useAuth();
|
|
const user = userProfile?.user ?? null;
|
|
// Driver.js tour is initialized and managed imperatively inside the hook
|
|
useOnboardingTour();
|
|
const { flyers, refetchFlyers, flyersError } = useFlyers();
|
|
const { masterItems, error: masterItemsError } = useMasterItems();
|
|
const {
|
|
shoppingLists,
|
|
activeListId,
|
|
setActiveListId,
|
|
createList,
|
|
deleteList,
|
|
addItemToList,
|
|
updateItemInList,
|
|
removeItemFromList,
|
|
error: shoppingListError,
|
|
} = useShoppingLists();
|
|
const {
|
|
watchedItems,
|
|
addWatchedItem,
|
|
removeWatchedItem,
|
|
error: watchedItemsError,
|
|
} = useWatchedItems();
|
|
const { totalActiveItems, error: activeDealsError } = useActiveDeals();
|
|
|
|
const handleActivityLogClick: ActivityLogClickHandler = useCallback(
|
|
(log) => {
|
|
if (log.action === 'list_shared') {
|
|
const listId = log.details.shopping_list_id;
|
|
if (shoppingLists.some((list) => list.shopping_list_id === listId)) {
|
|
setActiveListId(listId);
|
|
}
|
|
}
|
|
},
|
|
[shoppingLists, setActiveListId],
|
|
);
|
|
|
|
const handleAddItemToShoppingList = useCallback(
|
|
async (item: { masterItemId?: number; customItemName?: string }) => {
|
|
if (activeListId) {
|
|
await addItemToList(activeListId, item);
|
|
}
|
|
},
|
|
[activeListId, addItemToList],
|
|
);
|
|
|
|
const handleAddItemFromWatchedList = useCallback(
|
|
(masterItemId: number) => {
|
|
if (activeListId) {
|
|
addItemToList(activeListId, { masterItemId });
|
|
}
|
|
},
|
|
[activeListId, addItemToList],
|
|
);
|
|
|
|
// Consolidate error states into a single variable for cleaner display logic.
|
|
const combinedError =
|
|
flyersError?.message ||
|
|
masterItemsError ||
|
|
shoppingListError ||
|
|
watchedItemsError ||
|
|
activeDealsError;
|
|
|
|
// Only show banner for unauthenticated users when there are flyers to view
|
|
const shouldShowBanner = authStatus === 'SIGNED_OUT' && flyers.length > 0;
|
|
|
|
return (
|
|
<main className="max-w-screen-2xl mx-auto py-4 px-2.5 sm:py-6 lg:py-8">
|
|
{shouldShowBanner && (
|
|
<div className="max-w-5xl mx-auto mb-6 px-4 lg:px-0">
|
|
<AnonymousUserBanner onOpenProfile={onOpenProfile} />
|
|
</div>
|
|
)}
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8 items-start pb-16 lg:pb-0">
|
|
<div className="hidden lg:block lg:col-span-1 flex flex-col space-y-6">
|
|
<FlyerList
|
|
flyers={flyers}
|
|
onFlyerSelect={onFlyerSelect}
|
|
selectedFlyerId={selectedFlyerId}
|
|
profile={userProfile}
|
|
/>
|
|
<FlyerUploader onProcessingComplete={refetchFlyers} />
|
|
</div>
|
|
|
|
<div className="lg:col-span-2 flex flex-col space-y-6">
|
|
{combinedError && <ErrorDisplay message={combinedError} />}
|
|
{/* The Outlet will render the specific page content (e.g., FlyerDisplay or Welcome message) */}
|
|
<Outlet
|
|
context={{
|
|
totalActiveItems,
|
|
masterItems,
|
|
addWatchedItem,
|
|
shoppingLists,
|
|
activeListId,
|
|
addItemToList,
|
|
}}
|
|
/>
|
|
</div>
|
|
|
|
<div className="hidden lg:block lg:col-span-1 flex-col space-y-6">
|
|
<>
|
|
<ShoppingListComponent
|
|
user={user}
|
|
lists={shoppingLists}
|
|
activeListId={activeListId}
|
|
onSelectList={setActiveListId}
|
|
onCreateList={createList}
|
|
onDeleteList={deleteList}
|
|
onAddItem={handleAddItemToShoppingList}
|
|
onUpdateItem={updateItemInList}
|
|
onRemoveItem={removeItemFromList}
|
|
/>
|
|
<WatchedItemsList
|
|
items={watchedItems}
|
|
onAddItem={addWatchedItem}
|
|
onRemoveItem={removeWatchedItem}
|
|
user={user}
|
|
activeListId={activeListId}
|
|
onAddItemToList={handleAddItemFromWatchedList}
|
|
/>
|
|
<PriceChart
|
|
unitSystem={'imperial'} // This can be passed down or sourced from a context
|
|
user={user}
|
|
/>
|
|
{user && (
|
|
<>
|
|
<PriceHistoryChart />
|
|
<Leaderboard />
|
|
{userProfile?.role === 'admin' && (
|
|
<ActivityLog userProfile={userProfile} onLogClick={handleActivityLogClick} />
|
|
)}
|
|
</>
|
|
)}
|
|
</>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
);
|
|
};
|