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-jwt": "^4.0.1",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
"@types/pg": "^8.15.6",
|
"@types/pg": "^8.15.6",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
"@types/zxcvbn": "^4.4.5",
|
"@types/zxcvbn": "^4.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||||
"@typescript-eslint/parser": "^8.47.0",
|
"@typescript-eslint/parser": "^8.47.0",
|
||||||
@@ -4426,6 +4427,16 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"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": {
|
"node_modules/@types/send": {
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@
|
|||||||
"@types/passport-jwt": "^4.0.1",
|
"@types/passport-jwt": "^4.0.1",
|
||||||
"@types/passport-local": "^1.0.38",
|
"@types/passport-local": "^1.0.38",
|
||||||
"@types/pg": "^8.15.6",
|
"@types/pg": "^8.15.6",
|
||||||
|
"@types/react": "^19.2.7",
|
||||||
"@types/zxcvbn": "^4.4.5",
|
"@types/zxcvbn": "^4.4.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
||||||
"@typescript-eslint/parser": "^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();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
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;
|
if (!flyer.valid_from || !flyer.valid_to) return false;
|
||||||
try {
|
try {
|
||||||
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
||||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||||
return today >= from && today <= to;
|
return today >= from && today <= to;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error parsing flyer date", e);
|
logger.error("Error parsing flyer date", { error: e });
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -372,12 +372,12 @@ function App() {
|
|||||||
const validFlyerIds = validFlyers.map(f => f.flyer_id);
|
const validFlyerIds = validFlyers.map(f => f.flyer_id);
|
||||||
const allItems = await apiFetchFlyerItemsForFlyers(validFlyerIds);
|
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 =>
|
const dealItemsRaw = allItems.filter(item =>
|
||||||
item.master_item_id && watchedItemIds.has(item.master_item_id)
|
item.master_item_id && watchedItemIds.has(item.master_item_id)
|
||||||
); // This seems correct as it's comparing with 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 => ({
|
const deals: DealItem[] = dealItemsRaw.map(item => ({
|
||||||
item: item.item,
|
item: item.item,
|
||||||
@@ -412,14 +412,14 @@ function App() {
|
|||||||
const today = new Date();
|
const today = new Date();
|
||||||
today.setHours(0, 0, 0, 0);
|
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;
|
if (!flyer.valid_from || !flyer.valid_to) return false;
|
||||||
try {
|
try {
|
||||||
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
const from = new Date(`${flyer.valid_from}T00:00:00`);
|
||||||
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
const to = new Date(`${flyer.valid_to}T00:00:00`);
|
||||||
return today >= from && today <= to;
|
return today >= from && today <= to;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.error("Error parsing flyer date", e);
|
logger.error("Error parsing flyer date", { error: e });
|
||||||
return false;
|
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;
|
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
|
stageIndex += 2; // Increment by 2 for the stages we just completed. stageIndex is now 3
|
||||||
|
|
||||||
@@ -657,9 +662,8 @@ function App() {
|
|||||||
try {
|
try {
|
||||||
const updatedOrNewItem = await apiAddWatchedItem(itemName, category);
|
const updatedOrNewItem = await apiAddWatchedItem(itemName, category);
|
||||||
setWatchedItems(prevItems => {
|
setWatchedItems(prevItems => {
|
||||||
// The API returns an object with `id`, but our state uses `item_id`.
|
// Check if the item already exists in the state by its correct ID property.
|
||||||
// We compare the existing item's `item_id` with the new item's `id`.
|
const itemExists = prevItems.some(item => item.master_grocery_item_id === updatedOrNewItem.master_grocery_item_id);
|
||||||
const itemExists = prevItems.some(item => item.item_id === (updatedOrNewItem as any).id);
|
|
||||||
if (!itemExists) {
|
if (!itemExists) {
|
||||||
const newItems = [...prevItems, updatedOrNewItem]; // This was correct, but the check above was wrong.
|
const newItems = [...prevItems, updatedOrNewItem]; // This was correct, but the check above was wrong.
|
||||||
return newItems.sort((a,b) => a.name.localeCompare(b.name));
|
return newItems.sort((a,b) => a.name.localeCompare(b.name));
|
||||||
@@ -676,8 +680,8 @@ function App() {
|
|||||||
const handleRemoveWatchedItem = useCallback(async (masterItemId: number) => {
|
const handleRemoveWatchedItem = useCallback(async (masterItemId: number) => {
|
||||||
if (!user) return;
|
if (!user) return;
|
||||||
try {
|
try {
|
||||||
await apiRemoveWatchedItem(masterItemId);
|
await apiRemoveWatchedItem(masterItemId); // API call is correct
|
||||||
setWatchedItems(prevItems => prevItems.filter(item => item.item_id !== masterItemId));
|
setWatchedItems(prevItems => prevItems.filter(item => item.master_grocery_item_id !== masterItemId)); // State update must use correct property
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||||
setError(`Could not remove watched item: ${errorMessage}`);
|
setError(`Could not remove watched item: ${errorMessage}`);
|
||||||
@@ -718,9 +722,8 @@ function App() {
|
|||||||
setShoppingLists(prevLists => prevLists.map(list => {
|
setShoppingLists(prevLists => prevLists.map(list => {
|
||||||
if (list.shopping_list_id === listId) {
|
if (list.shopping_list_id === listId) {
|
||||||
// Avoid adding duplicates to the state if it's already there
|
// Avoid adding duplicates to the state if it's already there
|
||||||
// The API returns an object with `id`, but our state uses `item_id`.
|
// Check if the item already exists in the list by its correct ID property.
|
||||||
// We compare the existing item's `item_id` with the new item's `id`.
|
const itemExists = list.items.some(i => i.shopping_list_item_id === newItem.shopping_list_item_id);
|
||||||
const itemExists = list.items.some(i => i.item_id === (newItem as any).id);
|
|
||||||
if (itemExists) return list;
|
if (itemExists) return list;
|
||||||
return { ...list, items: [...list.items, newItem] };
|
return { ...list, items: [...list.items, newItem] };
|
||||||
}
|
}
|
||||||
@@ -738,7 +741,7 @@ function App() {
|
|||||||
const updatedItem = await apiUpdateShoppingListItem(itemId, updates);
|
const updatedItem = await apiUpdateShoppingListItem(itemId, updates);
|
||||||
setShoppingLists(prevLists => prevLists.map(list => {
|
setShoppingLists(prevLists => prevLists.map(list => {
|
||||||
if (list.shopping_list_id === activeListId) {
|
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;
|
return list;
|
||||||
}));
|
}));
|
||||||
@@ -754,7 +757,7 @@ function App() {
|
|||||||
await apiRemoveShoppingListItem(itemId);
|
await apiRemoveShoppingListItem(itemId);
|
||||||
setShoppingLists(prevLists => prevLists.map(list => {
|
setShoppingLists(prevLists => prevLists.map(list => {
|
||||||
if (list.shopping_list_id === activeListId) {
|
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;
|
return list;
|
||||||
}));
|
}));
|
||||||
@@ -772,10 +775,10 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleActivityLogClick: ActivityLogClickHandler = (log) => {
|
const handleActivityLogClick: ActivityLogClickHandler = (log) => {
|
||||||
if (log.activity_type === 'share_shopping_list' && log.entity_id) {
|
if (log.action === 'list_shared' && log.details?.shopping_list_id) {
|
||||||
const listId = parseInt(log.entity_id, 10);
|
const listId = parseInt(String(log.details.shopping_list_id), 10);
|
||||||
// Check if the list exists before setting it as active. This was correct.
|
// 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);
|
setActiveListId(listId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -880,7 +883,7 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="lg:col-span-2 flex flex-col space-y-6">
|
<div className="lg:col-span-2 flex flex-col space-y-6">
|
||||||
<ErrorDisplay message={error} />
|
{error && <ErrorDisplay message={error} />}
|
||||||
|
|
||||||
{isProcessing ? (
|
{isProcessing ? (
|
||||||
<ProcessingStatus
|
<ProcessingStatus
|
||||||
|
|||||||
@@ -302,7 +302,7 @@ export const processFlyerFile = async (
|
|||||||
flyerImage: File,
|
flyerImage: File,
|
||||||
checksum: string,
|
checksum: string,
|
||||||
originalFileName: 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 }> => {
|
): Promise<{ message: string; flyer: Flyer }> => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('flyerImage', flyerImage);
|
formData.append('flyerImage', flyerImage);
|
||||||
@@ -1293,10 +1293,10 @@ export async function deleteUserAccount(password: string, tokenOverride?: string
|
|||||||
try {
|
try {
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
errorMessage = data.message || errorMessage;
|
errorMessage = data.message || errorMessage;
|
||||||
} catch (e) {
|
} catch (jsonError) {
|
||||||
// Response was not JSON (likely 404 HTML or 500 text)
|
// Response was not JSON (likely 404 HTML or 500 text)
|
||||||
const text = await response.clone().text(); // Use clone() to avoid "Body already read" error
|
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}`;
|
errorMessage += `: ${response.status} ${response.statusText}`;
|
||||||
}
|
}
|
||||||
throw new Error(errorMessage);
|
throw new Error(errorMessage);
|
||||||
|
|||||||
@@ -14,7 +14,8 @@ const { test: _unusedTest, ...baseViteConfig } = viteConfig as any;
|
|||||||
console.error('\n[DEBUG] --- INTEGRATION CONFIG SETUP ---');
|
console.error('\n[DEBUG] --- INTEGRATION CONFIG SETUP ---');
|
||||||
// Use _unusedTest to satisfy linter
|
// Use _unusedTest to satisfy linter
|
||||||
console.error(`[DEBUG] Stripped "test" config. Original test config existed: ${!!_unusedTest}`);
|
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.
|
* This configuration is specifically for integration tests.
|
||||||
|
|||||||
Reference in New Issue
Block a user