some more re-org + fixes
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m55s
All checks were successful
Deploy to Web Server flyer-crawler.projectium.com / deploy (push) Successful in 3m55s
This commit is contained in:
11
package-lock.json
generated
11
package-lock.json
generated
@@ -51,6 +51,7 @@
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/pg": "^8.15.6",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/zxcvbn": "^4.4.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||
"@typescript-eslint/parser": "^8.47.0",
|
||||
@@ -4426,6 +4427,16 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/send": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
"@types/passport-jwt": "^4.0.1",
|
||||
"@types/passport-local": "^1.0.38",
|
||||
"@types/pg": "^8.15.6",
|
||||
"@types/react": "^19.2.7",
|
||||
"@types/zxcvbn": "^4.4.5",
|
||||
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||
"@typescript-eslint/parser": "^8.47.0",
|
||||
|
||||
43
src/App.tsx
43
src/App.tsx
@@ -352,14 +352,14 @@ function App() {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const validFlyers = flyers.filter(flyer => {
|
||||
const validFlyers = flyers.filter((flyer: Flyer) => {
|
||||
if (!flyer.valid_from || !flyer.valid_to) return false;
|
||||
try {
|
||||
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||
return today >= from && today <= to;
|
||||
} catch (e) {
|
||||
logger.error("Error parsing flyer date", e);
|
||||
logger.error("Error parsing flyer date", { error: e });
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -372,12 +372,12 @@ function App() {
|
||||
const validFlyerIds = validFlyers.map(f => f.flyer_id);
|
||||
const allItems = await apiFetchFlyerItemsForFlyers(validFlyerIds);
|
||||
|
||||
const watchedItemIds = new Set(watchedItems.map(item => item.item_id));
|
||||
const watchedItemIds = new Set(watchedItems.map((item: MasterGroceryItem) => item.master_grocery_item_id));
|
||||
const dealItemsRaw = allItems.filter(item =>
|
||||
item.master_item_id && watchedItemIds.has(item.master_item_id)
|
||||
); // This seems correct as it's comparing with master_item_id
|
||||
|
||||
const flyerIdToStoreName = new Map(validFlyers.map(f => [f.flyer_id, f.store?.name || 'Unknown Store']));
|
||||
const flyerIdToStoreName = new Map(validFlyers.map((f: Flyer) => [f.flyer_id, f.store?.name || 'Unknown Store']));
|
||||
|
||||
const deals: DealItem[] = dealItemsRaw.map(item => ({
|
||||
item: item.item,
|
||||
@@ -412,14 +412,14 @@ function App() {
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
const validFlyers = flyers.filter(flyer => {
|
||||
const validFlyers = flyers.filter((flyer: Flyer) => {
|
||||
if (!flyer.valid_from || !flyer.valid_to) return false;
|
||||
try {
|
||||
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||
return today >= from && today <= to;
|
||||
} catch (e) {
|
||||
logger.error("Error parsing flyer date", e);
|
||||
logger.error("Error parsing flyer date", { error: e });
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -494,6 +494,11 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
// If extractedData is null or undefined at this point, it means the AI call failed.
|
||||
if (!extractedData) {
|
||||
throw new Error("Core data extraction failed. The AI did not return valid data.");
|
||||
}
|
||||
|
||||
const { store_name, valid_from, valid_to, items: extractedItems } = extractedData;
|
||||
stageIndex += 2; // Increment by 2 for the stages we just completed. stageIndex is now 3
|
||||
|
||||
@@ -657,9 +662,8 @@ function App() {
|
||||
try {
|
||||
const updatedOrNewItem = await apiAddWatchedItem(itemName, category);
|
||||
setWatchedItems(prevItems => {
|
||||
// The API returns an object with `id`, but our state uses `item_id`.
|
||||
// We compare the existing item's `item_id` with the new item's `id`.
|
||||
const itemExists = prevItems.some(item => item.item_id === (updatedOrNewItem as any).id);
|
||||
// Check if the item already exists in the state by its correct ID property.
|
||||
const itemExists = prevItems.some(item => item.master_grocery_item_id === updatedOrNewItem.master_grocery_item_id);
|
||||
if (!itemExists) {
|
||||
const newItems = [...prevItems, updatedOrNewItem]; // This was correct, but the check above was wrong.
|
||||
return newItems.sort((a,b) => a.name.localeCompare(b.name));
|
||||
@@ -676,8 +680,8 @@ function App() {
|
||||
const handleRemoveWatchedItem = useCallback(async (masterItemId: number) => {
|
||||
if (!user) return;
|
||||
try {
|
||||
await apiRemoveWatchedItem(masterItemId);
|
||||
setWatchedItems(prevItems => prevItems.filter(item => item.item_id !== masterItemId));
|
||||
await apiRemoveWatchedItem(masterItemId); // API call is correct
|
||||
setWatchedItems(prevItems => prevItems.filter(item => item.master_grocery_item_id !== masterItemId)); // State update must use correct property
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
setError(`Could not remove watched item: ${errorMessage}`);
|
||||
@@ -718,9 +722,8 @@ function App() {
|
||||
setShoppingLists(prevLists => prevLists.map(list => {
|
||||
if (list.shopping_list_id === listId) {
|
||||
// Avoid adding duplicates to the state if it's already there
|
||||
// The API returns an object with `id`, but our state uses `item_id`.
|
||||
// We compare the existing item's `item_id` with the new item's `id`.
|
||||
const itemExists = list.items.some(i => i.item_id === (newItem as any).id);
|
||||
// Check if the item already exists in the list by its correct ID property.
|
||||
const itemExists = list.items.some(i => i.shopping_list_item_id === newItem.shopping_list_item_id);
|
||||
if (itemExists) return list;
|
||||
return { ...list, items: [...list.items, newItem] };
|
||||
}
|
||||
@@ -738,7 +741,7 @@ function App() {
|
||||
const updatedItem = await apiUpdateShoppingListItem(itemId, updates);
|
||||
setShoppingLists(prevLists => prevLists.map(list => {
|
||||
if (list.shopping_list_id === activeListId) {
|
||||
return { ...list, items: list.items.map(i => i.item_id === itemId ? updatedItem : i) };
|
||||
return { ...list, items: list.items.map(i => i.shopping_list_item_id === itemId ? updatedItem : i) };
|
||||
}
|
||||
return list;
|
||||
}));
|
||||
@@ -754,7 +757,7 @@ function App() {
|
||||
await apiRemoveShoppingListItem(itemId);
|
||||
setShoppingLists(prevLists => prevLists.map(list => {
|
||||
if (list.shopping_list_id === activeListId) {
|
||||
return { ...list, items: list.items.filter(i => i.item_id !== itemId) };
|
||||
return { ...list, items: list.items.filter(i => i.shopping_list_item_id !== itemId) };
|
||||
}
|
||||
return list;
|
||||
}));
|
||||
@@ -772,10 +775,10 @@ function App() {
|
||||
};
|
||||
|
||||
const handleActivityLogClick: ActivityLogClickHandler = (log) => {
|
||||
if (log.activity_type === 'share_shopping_list' && log.entity_id) {
|
||||
const listId = parseInt(log.entity_id, 10);
|
||||
if (log.action === 'list_shared' && log.details?.shopping_list_id) {
|
||||
const listId = parseInt(String(log.details.shopping_list_id), 10);
|
||||
// Check if the list exists before setting it as active. This was correct.
|
||||
if (shoppingLists.some(list => list.shopping_list_id === listId)) {
|
||||
if (shoppingLists.some(list => list.shopping_list_id === listId) && typeof listId === 'number') {
|
||||
setActiveListId(listId);
|
||||
}
|
||||
}
|
||||
@@ -880,7 +883,7 @@ function App() {
|
||||
</div>
|
||||
|
||||
<div className="lg:col-span-2 flex flex-col space-y-6">
|
||||
<ErrorDisplay message={error} />
|
||||
{error && <ErrorDisplay message={error} />}
|
||||
|
||||
{isProcessing ? (
|
||||
<ProcessingStatus
|
||||
|
||||
@@ -302,7 +302,7 @@ export const processFlyerFile = async (
|
||||
flyerImage: File,
|
||||
checksum: string,
|
||||
originalFileName: string,
|
||||
extractedData: { store_name: string; valid_from: string; valid_to: string; items: Omit<FlyerItem, 'id' | 'flyer_id' | 'created_at'>[]; store_address: string | null }
|
||||
extractedData: { store_name: string; valid_from: string | null; valid_to: string | null; items: Omit<FlyerItem, 'flyer_item_id' | 'flyer_id' | 'created_at'>[]; store_address: string | null }
|
||||
): Promise<{ message: string; flyer: Flyer }> => {
|
||||
const formData = new FormData();
|
||||
formData.append('flyerImage', flyerImage);
|
||||
@@ -1293,10 +1293,10 @@ export async function deleteUserAccount(password: string, tokenOverride?: string
|
||||
try {
|
||||
const data = await response.json();
|
||||
errorMessage = data.message || errorMessage;
|
||||
} catch (e) {
|
||||
} catch (jsonError) {
|
||||
// Response was not JSON (likely 404 HTML or 500 text)
|
||||
const text = await response.clone().text(); // Use clone() to avoid "Body already read" error
|
||||
logger.error(`deleteUserAccount failed with status ${response.status}. Body: ${text}`);
|
||||
logger.error(`deleteUserAccount failed with status ${response.status}. Could not parse JSON response. Body: ${text}`, { jsonError });
|
||||
errorMessage += `: ${response.status} ${response.statusText}`;
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
|
||||
@@ -14,7 +14,8 @@ const { test: _unusedTest, ...baseViteConfig } = viteConfig as any;
|
||||
console.error('\n[DEBUG] --- INTEGRATION CONFIG SETUP ---');
|
||||
// Use _unusedTest to satisfy linter
|
||||
console.error(`[DEBUG] Stripped "test" config. Original test config existed: ${!!_unusedTest}`);
|
||||
console.error('[DEBUG] Does baseViteConfig have "test"?', !!(baseViteConfig as any).test);
|
||||
// Use the 'in' operator for a type-safe property check instead of casting to 'any'.
|
||||
console.error('[DEBUG] Does baseViteConfig have "test"?', 'test' in baseViteConfig);
|
||||
|
||||
/**
|
||||
* This configuration is specifically for integration tests.
|
||||
|
||||
Reference in New Issue
Block a user