Files
flyer-crawler.projectium.com/src/hooks/useShoppingLists.tsx
Torben Sorensen 25d6b76f6d
Some checks failed
Deploy to Test Environment / deploy-to-test (push) Has been cancelled
ADR-026: Client-Side Logging + linting fixes
2026-01-09 17:58:21 -08:00

189 lines
6.2 KiB
TypeScript

// src/hooks/useShoppingLists.tsx
import { useState, useCallback, useEffect, useMemo } from 'react';
import { useAuth } from '../hooks/useAuth';
import { useUserData } from '../hooks/useUserData';
import {
useCreateShoppingListMutation,
useDeleteShoppingListMutation,
useAddShoppingListItemMutation,
useUpdateShoppingListItemMutation,
useRemoveShoppingListItemMutation,
} from './mutations';
import { logger } from '../services/logger.client';
import type { ShoppingListItem } from '../types';
/**
* A custom hook to manage all state and logic related to shopping lists.
*
* This hook has been refactored to use TanStack Query mutations (ADR-0005 Phase 4).
* It provides a simplified interface for shopping list operations with:
* - Automatic cache invalidation
* - Success/error notifications
* - No manual state management
*
* The interface remains backward compatible with the previous implementation.
*/
const useShoppingListsHook = () => {
const { userProfile } = useAuth();
const { shoppingLists } = useUserData();
// Local state for tracking the active list (UI concern, not server state)
const [activeListId, setActiveListId] = useState<number | null>(null);
// TanStack Query mutation hooks
const createListMutation = useCreateShoppingListMutation();
const deleteListMutation = useDeleteShoppingListMutation();
const addItemMutation = useAddShoppingListItemMutation();
const updateItemMutation = useUpdateShoppingListItemMutation();
const removeItemMutation = useRemoveShoppingListItemMutation();
// Consolidate errors from all mutations
const error = useMemo(() => {
const errors = [
createListMutation.error,
deleteListMutation.error,
addItemMutation.error,
updateItemMutation.error,
removeItemMutation.error,
];
const firstError = errors.find((err) => err !== null);
return firstError?.message || null;
}, [
createListMutation.error,
deleteListMutation.error,
addItemMutation.error,
updateItemMutation.error,
removeItemMutation.error,
]);
// Effect to select the first list as active when lists are loaded or the user changes.
useEffect(() => {
// Check if the currently active list still exists in the shoppingLists array.
const activeListExists = shoppingLists.some((l) => l.shopping_list_id === activeListId);
// If the user is logged in and there are lists...
if (userProfile && shoppingLists.length > 0) {
// ...but no list is active, or the active one was deleted, select the first available list.
if (!activeListExists) {
setActiveListId(shoppingLists[0].shopping_list_id);
}
} else if (activeListId !== null) {
// If there's no user or no lists, ensure no list is active.
setActiveListId(null);
}
}, [shoppingLists, userProfile, activeListId]);
/**
* Create a new shopping list.
* Uses TanStack Query mutation which automatically invalidates the cache.
*/
const createList = useCallback(
async (name: string) => {
if (!userProfile) return;
try {
await createListMutation.mutateAsync({ name });
} catch (error) {
// Error is already handled by the mutation hook (notification shown)
logger.error({ err: error }, '[useShoppingLists] Failed to create list');
}
},
[userProfile, createListMutation],
);
/**
* Delete a shopping list.
* Uses TanStack Query mutation which automatically invalidates the cache.
*/
const deleteList = useCallback(
async (listId: number) => {
if (!userProfile) return;
try {
await deleteListMutation.mutateAsync({ listId });
} catch (error) {
// Error is already handled by the mutation hook (notification shown)
logger.error({ err: error }, '[useShoppingLists] Failed to delete list');
}
},
[userProfile, deleteListMutation],
);
/**
* Add an item to a shopping list.
* Uses TanStack Query mutation which automatically invalidates the cache.
*
* Note: Duplicate checking has been moved to the server-side.
* The API will handle duplicate detection and return appropriate errors.
*/
const addItemToList = useCallback(
async (listId: number, item: { masterItemId?: number; customItemName?: string }) => {
if (!userProfile) return;
try {
await addItemMutation.mutateAsync({ listId, item });
} catch (error) {
// Error is already handled by the mutation hook (notification shown)
logger.error({ err: error }, '[useShoppingLists] Failed to add item');
}
},
[userProfile, addItemMutation],
);
/**
* Update a shopping list item (quantity, purchased status, notes, etc).
* Uses TanStack Query mutation which automatically invalidates the cache.
*/
const updateItemInList = useCallback(
async (itemId: number, updates: Partial<ShoppingListItem>) => {
if (!userProfile) return;
try {
await updateItemMutation.mutateAsync({ itemId, updates });
} catch (error) {
// Error is already handled by the mutation hook (notification shown)
logger.error({ err: error }, '[useShoppingLists] Failed to update item');
}
},
[userProfile, updateItemMutation],
);
/**
* Remove an item from a shopping list.
* Uses TanStack Query mutation which automatically invalidates the cache.
*/
const removeItemFromList = useCallback(
async (itemId: number) => {
if (!userProfile) return;
try {
await removeItemMutation.mutateAsync({ itemId });
} catch (error) {
// Error is already handled by the mutation hook (notification shown)
logger.error({ err: error }, '[useShoppingLists] Failed to remove item');
}
},
[userProfile, removeItemMutation],
);
return {
shoppingLists,
activeListId,
setActiveListId,
createList,
deleteList,
addItemToList,
updateItemInList,
removeItemFromList,
// Loading states from mutations
isCreatingList: createListMutation.isPending,
isDeletingList: deleteListMutation.isPending,
isAddingItem: addItemMutation.isPending,
isUpdatingItem: updateItemMutation.isPending,
isRemovingItem: removeItemMutation.isPending,
error,
};
};
export { useShoppingListsHook as useShoppingLists };