Add target price alerts, historical low indicator, bulk actions, and dashboard summary

Features:
- Target price alerts: Set a specific price target and get notified when reached
- Historical low indicator: Badge showing when current price is at/near all-time low
- Bulk actions: Select multiple products to delete at once
- Dashboard summary: Shows total products, items at lowest price, at target, biggest drops

Backend changes:
- Add target_price column to products table
- Add target_price notification type with Telegram/Discord support
- Include min_price in product queries for historical low detection
- Update scheduler to check target price conditions

Frontend changes:
- Add target price input to ProductDetail notification settings
- Show target price badge on product cards
- Add "Lowest Price" and "Near Low" badges to product cards
- Add bulk selection mode with checkboxes
- Add dashboard summary cards at top of product list

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
clucraft 2026-01-21 13:40:39 -05:00
parent 2acc47c21c
commit a85e22d8bc
9 changed files with 454 additions and 5 deletions

View file

@ -3,11 +3,12 @@ import axios from 'axios';
export interface NotificationPayload {
productName: string;
productUrl: string;
type: 'price_drop' | 'back_in_stock';
type: 'price_drop' | 'back_in_stock' | 'target_price';
oldPrice?: number;
newPrice?: number;
currency?: string;
threshold?: number;
targetPrice?: number;
}
function formatMessage(payload: NotificationPayload): string {
@ -27,6 +28,16 @@ function formatMessage(payload: NotificationPayload): string {
`🔗 ${payload.productUrl}`;
}
if (payload.type === 'target_price') {
const newPriceStr = payload.newPrice ? `${currencySymbol}${payload.newPrice.toFixed(2)}` : 'N/A';
const targetPriceStr = payload.targetPrice ? `${currencySymbol}${payload.targetPrice.toFixed(2)}` : 'N/A';
return `🎯 Target Price Reached!\n\n` +
`📦 ${payload.productName}\n\n` +
`💰 Price is now ${newPriceStr} (your target: ${targetPriceStr})\n\n` +
`🔗 ${payload.productUrl}`;
}
if (payload.type === 'back_in_stock') {
const priceStr = payload.newPrice ? ` at ${currencySymbol}${payload.newPrice.toFixed(2)}` : '';
return `🎉 Back in Stock!\n\n` +
@ -85,6 +96,21 @@ export async function sendDiscordNotification(
url: payload.productUrl,
timestamp: new Date().toISOString(),
};
} else if (payload.type === 'target_price') {
const newPriceStr = payload.newPrice ? `${currencySymbol}${payload.newPrice.toFixed(2)}` : 'N/A';
const targetPriceStr = payload.targetPrice ? `${currencySymbol}${payload.targetPrice.toFixed(2)}` : 'N/A';
embed = {
title: '🎯 Target Price Reached!',
description: payload.productName,
color: 0xf59e0b, // Amber
fields: [
{ name: 'Current Price', value: newPriceStr, inline: true },
{ name: 'Your Target', value: targetPriceStr, inline: true },
],
url: payload.productUrl,
timestamp: new Date().toISOString(),
};
} else {
const priceStr = payload.newPrice ? `${currencySymbol}${payload.newPrice.toFixed(2)}` : 'Check link';