diff --git a/backend/src/routes/products.ts b/backend/src/routes/products.ts index fac5724..d91ff34 100644 --- a/backend/src/routes/products.ts +++ b/backend/src/routes/products.ts @@ -89,24 +89,40 @@ router.post('/', async (req: AuthRequest, res: Response) => { return; } - // If needsReview is true and there are multiple candidates, return them for user selection - if (scrapedData.needsReview && scrapedData.priceCandidates.length > 1) { - res.status(200).json({ - needsReview: true, - name: scrapedData.name, - imageUrl: scrapedData.imageUrl, - stockStatus: scrapedData.stockStatus, - priceCandidates: scrapedData.priceCandidates.map(c => ({ - price: c.price, - currency: c.currency, - method: c.method, - context: c.context, - confidence: c.confidence, - })), - suggestedPrice: scrapedData.price, - url, - }); - return; + // Always show price selection modal when adding a product so user can verify + // Show if we have at least one candidate with a price + if (scrapedData.priceCandidates.length > 0 || scrapedData.price) { + // Make sure we have at least one candidate to show + const candidates = scrapedData.priceCandidates.length > 0 + ? scrapedData.priceCandidates + : scrapedData.price + ? [{ + price: scrapedData.price.price, + currency: scrapedData.price.currency, + method: scrapedData.selectedMethod || 'ai' as const, + context: 'Extracted price', + confidence: 0.8 + }] + : []; + + if (candidates.length > 0) { + res.status(200).json({ + needsReview: true, + name: scrapedData.name, + imageUrl: scrapedData.imageUrl, + stockStatus: scrapedData.stockStatus, + priceCandidates: candidates.map(c => ({ + price: c.price, + currency: c.currency, + method: c.method, + context: c.context, + confidence: c.confidence, + })), + suggestedPrice: scrapedData.price, + url, + }); + return; + } } // Create product with stock status diff --git a/backend/src/services/scraper.ts b/backend/src/services/scraper.ts index 1617883..45007a6 100644 --- a/backend/src/services/scraper.ts +++ b/backend/src/services/scraper.ts @@ -1295,12 +1295,27 @@ export async function scrapeProductWithVoting( let html: string = ''; + // Sites known to require JavaScript rendering + const jsHeavySites = [ + /bestbuy\.com/i, + /target\.com/i, + /walmart\.com/i, + /costco\.com/i, + ]; + const requiresBrowser = jsHeavySites.some(pattern => pattern.test(url)); + try { let usedBrowser = false; - // Fetch HTML - try { - const response = await axios.get(url, { + // For JS-heavy sites, go straight to browser + if (requiresBrowser) { + console.log(`[Voting] ${new URL(url).hostname} requires browser rendering, using Puppeteer...`); + html = await scrapeWithBrowser(url); + usedBrowser = true; + } else { + // Fetch HTML + try { + const response = await axios.get(url, { headers: { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', @@ -1323,13 +1338,14 @@ export async function scrapeProductWithVoting( maxRedirects: 5, }); html = response.data; - } catch (axiosError) { - if (axiosError instanceof AxiosError && axiosError.response?.status === 403) { - console.log(`[Voting] HTTP blocked (403) for ${url}, using browser...`); - html = await scrapeWithBrowser(url); - usedBrowser = true; - } else { - throw axiosError; + } catch (axiosError) { + if (axiosError instanceof AxiosError && axiosError.response?.status === 403) { + console.log(`[Voting] HTTP blocked (403) for ${url}, using browser...`); + html = await scrapeWithBrowser(url); + usedBrowser = true; + } else { + throw axiosError; + } } } diff --git a/frontend/src/components/PriceSelectionModal.tsx b/frontend/src/components/PriceSelectionModal.tsx index 04bbd9b..ea559c8 100644 --- a/frontend/src/components/PriceSelectionModal.tsx +++ b/frontend/src/components/PriceSelectionModal.tsx @@ -265,9 +265,13 @@ export default function PriceSelectionModal({
-

Multiple Prices Found

+

+ {candidates.length > 1 ? 'Multiple Prices Found' : 'Confirm Price'} +

- We found different prices for this product. Please select the correct one. + {candidates.length > 1 + ? 'We found different prices for this product. Please select the correct one.' + : 'Please verify this is the correct price for the product.'}

diff --git a/frontend/tsconfig.tsbuildinfo b/frontend/tsconfig.tsbuildinfo index b9f18e1..2f1fdbe 100644 --- a/frontend/tsconfig.tsbuildinfo +++ b/frontend/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/components/authform.tsx","./src/components/layout.tsx","./src/components/passwordinput.tsx","./src/components/pricechart.tsx","./src/components/productcard.tsx","./src/components/productform.tsx","./src/components/sparkline.tsx","./src/components/stocktimeline.tsx","./src/context/authcontext.tsx","./src/context/toastcontext.tsx","./src/hooks/useauth.ts","./src/pages/dashboard.tsx","./src/pages/login.tsx","./src/pages/productdetail.tsx","./src/pages/register.tsx","./src/pages/settings.tsx"],"version":"5.9.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/components/aistatusbadge.tsx","./src/components/authform.tsx","./src/components/layout.tsx","./src/components/notificationbell.tsx","./src/components/particlebackground.tsx","./src/components/passwordinput.tsx","./src/components/pricechart.tsx","./src/components/priceselectionmodal.tsx","./src/components/productcard.tsx","./src/components/productform.tsx","./src/components/sparkline.tsx","./src/components/stocktimeline.tsx","./src/context/authcontext.tsx","./src/context/toastcontext.tsx","./src/hooks/useauth.ts","./src/pages/dashboard.tsx","./src/pages/login.tsx","./src/pages/notificationhistory.tsx","./src/pages/productdetail.tsx","./src/pages/register.tsx","./src/pages/settings.tsx"],"version":"5.9.3"} \ No newline at end of file