fix: Always show price selection modal and use browser for JS-heavy sites

- Always show price selection modal when adding a product so users can verify
- Use browser rendering for known JS-heavy sites (Best Buy, Target, Walmart, Costco)
- Update modal text to say "Confirm Price" for single candidates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-24 14:58:58 -05:00
parent 4fd04cd160
commit cf23ac9db1
4 changed files with 67 additions and 31 deletions

View file

@ -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

View file

@ -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<string>(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<string>(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;
}
}
}