diff --git a/index.html b/index.html index dcaad83..39be13c 100644 --- a/index.html +++ b/index.html @@ -3,12 +3,12 @@ - specseek-tracker - - + Hardware Price Tracker - Real-time Monitoring & Alerts + + - - + + diff --git a/src/components/AlertsList.tsx b/src/components/AlertsList.tsx new file mode 100644 index 0000000..d32bf27 --- /dev/null +++ b/src/components/AlertsList.tsx @@ -0,0 +1,121 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { AlertCircle, ExternalLink } from "lucide-react"; + +interface AlertsListProps { + limit?: number; +} + +export const AlertsList = ({ limit }: AlertsListProps) => { + // Mock data + const alerts = [ + { + id: 1, + type: "product", + sourceTitle: "Samsung 990 PRO 2TB NVMe SSD", + message: "Price dropped to 699 DKK (target: 750 DKK)", + store: "proshop", + price: 699, + targetPrice: 750, + currency: "DKK", + url: "https://www.proshop.dk/example", + createdAt: "5 min ago", + }, + { + id: 2, + type: "search_profile", + sourceTitle: "32GB DDR5-6000 RAM", + message: "Corsair Vengeance 32GB DDR5-6000 @ 899 DKK (target: 1000 DKK)", + store: "proshop", + price: 899, + targetPrice: 1000, + currency: "DKK", + url: "https://www.proshop.dk/example", + createdAt: "1 hour ago", + }, + { + id: 3, + type: "search_profile", + sourceTitle: "1TB NVMe SSD Budget", + message: "Crucial P3 1TB @ 449 DKK (target: 500 DKK)", + store: "proshop", + price: 449, + targetPrice: 500, + currency: "DKK", + url: "https://www.proshop.dk/example", + createdAt: "3 hours ago", + }, + { + id: 4, + type: "product", + sourceTitle: "WD Black SN850X 2TB NVMe", + message: "Price dropped to 1399 DKK (target: 1400 DKK)", + store: "proshop", + price: 1399, + targetPrice: 1400, + currency: "DKK", + url: "https://www.proshop.dk/example", + createdAt: "5 hours ago", + }, + { + id: 5, + type: "product", + sourceTitle: "G.Skill Trident Z5 32GB DDR5-6400", + message: "Price dropped to 1099 DKK (target: 1200 DKK)", + store: "proshop", + price: 1099, + targetPrice: 1200, + currency: "DKK", + url: "https://www.proshop.dk/example", + createdAt: "8 hours ago", + }, + ]; + + const displayAlerts = limit ? alerts.slice(0, limit) : alerts; + + const getDiscountPercent = (price: number, target: number) => { + return (((target - price) / target) * 100).toFixed(1); + }; + + return ( +
+ {displayAlerts.map((alert) => ( +
+
+ +
+ +
+
+

{alert.sourceTitle}

+ + {alert.type.replace("_", " ")} + +
+ +

{alert.message}

+ +
+ {alert.store} + + {alert.createdAt} + + + {getDiscountPercent(alert.price, alert.targetPrice)}% below target + +
+
+ + +
+ ))} +
+ ); +}; diff --git a/src/components/PriceChart.tsx b/src/components/PriceChart.tsx new file mode 100644 index 0000000..99c0410 --- /dev/null +++ b/src/components/PriceChart.tsx @@ -0,0 +1,73 @@ +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, Legend } from "recharts"; + +export const PriceChart = () => { + // Mock data - will be replaced with real price history + const data = [ + { date: "Jan 1", samsung: 1499, wd: 1499, crucial: 599 }, + { date: "Jan 3", samsung: 1449, wd: 1450, crucial: 579 }, + { date: "Jan 5", samsung: 1399, wd: 1450, crucial: 569 }, + { date: "Jan 7", samsung: 1349, wd: 1399, crucial: 549 }, + { date: "Jan 9", samsung: 1299, wd: 1450, crucial: 549 }, + ]; + + const CustomTooltip = ({ active, payload, label }: any) => { + if (active && payload && payload.length) { + return ( +
+

{label}

+ {payload.map((entry: any, index: number) => ( +

+ {entry.name}: {entry.value} DKK +

+ ))} +
+ ); + } + return null; + }; + + return ( + + + + + + } /> + + + + + + + ); +}; diff --git a/src/components/ProductsList.tsx b/src/components/ProductsList.tsx new file mode 100644 index 0000000..31f0328 --- /dev/null +++ b/src/components/ProductsList.tsx @@ -0,0 +1,151 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { ExternalLink, TrendingDown, TrendingUp } from "lucide-react"; + +interface ProductsListProps { + limit?: number; + sortBy?: "date" | "deal"; +} + +export const ProductsList = ({ limit, sortBy = "date" }: ProductsListProps) => { + // Mock data - will be replaced with real data from Lovable Cloud + const products = [ + { + id: 1, + name: "Samsung 990 PRO 2TB NVMe SSD", + store: "proshop", + currentPrice: 1299, + targetPrice: 1500, + lastPrice: 1399, + currency: "DKK", + url: "https://www.proshop.dk/example", + lastChecked: "2 min ago", + availability: "in_stock", + }, + { + id: 2, + name: "Corsair Vengeance RGB 32GB DDR5-6000", + store: "proshop", + currentPrice: 899, + targetPrice: 1000, + lastPrice: 949, + currency: "DKK", + url: "https://www.proshop.dk/example", + lastChecked: "5 min ago", + availability: "in_stock", + }, + { + id: 3, + name: "WD Black SN850X 2TB NVMe", + store: "proshop", + currentPrice: 1450, + targetPrice: 1400, + lastPrice: 1399, + currency: "DKK", + url: "https://www.proshop.dk/example", + lastChecked: "8 min ago", + availability: "in_stock", + }, + { + id: 4, + name: "G.Skill Trident Z5 32GB DDR5-6400", + store: "proshop", + currentPrice: 1099, + targetPrice: 1200, + lastPrice: 1149, + currency: "DKK", + url: "https://www.proshop.dk/example", + lastChecked: "12 min ago", + availability: "in_stock", + }, + { + id: 5, + name: "Crucial P5 Plus 1TB NVMe", + store: "proshop", + currentPrice: 549, + targetPrice: 600, + lastPrice: 599, + currency: "DKK", + url: "https://www.proshop.dk/example", + lastChecked: "15 min ago", + availability: "in_stock", + }, + ]; + + const displayProducts = limit ? products.slice(0, limit) : products; + + const getPriceChange = (current: number, last: number) => { + const change = ((current - last) / last) * 100; + return change.toFixed(1); + }; + + const isPriceDown = (current: number, last: number) => current < last; + + const isBelowTarget = (current: number, target: number) => current <= target; + + return ( +
+ {displayProducts.map((product) => { + const priceChange = getPriceChange(product.currentPrice, product.lastPrice); + const priceDown = isPriceDown(product.currentPrice, product.lastPrice); + const belowTarget = isBelowTarget(product.currentPrice, product.targetPrice); + + return ( +
+
+
+

{product.name}

+ {belowTarget && ( + + Target Hit + + )} + {product.availability === "in_stock" && ( + + In Stock + + )} +
+
+ {product.store} + + Target: {product.targetPrice} {product.currency} + + {product.lastChecked} +
+
+ +
+
+
+ + {product.currentPrice} {product.currency} + +
+
+ {priceDown ? ( + + ) : ( + + )} + + {priceDown ? "" : "+"}{priceChange}% + +
+
+ + +
+
+ ); + })} +
+ ); +}; diff --git a/src/components/SearchProfiles.tsx b/src/components/SearchProfiles.tsx new file mode 100644 index 0000000..36ad737 --- /dev/null +++ b/src/components/SearchProfiles.tsx @@ -0,0 +1,162 @@ +import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; +import { Search, TrendingDown } from "lucide-react"; + +export const SearchProfiles = () => { + // Mock data + const profiles = [ + { + id: 1, + title: "2TB NVMe SSD", + category: "ssd", + filters: { + capacity_gb: 2000, + interface: "NVMe", + brand: ["Samsung", "Crucial"], + }, + targetPrice: 750, + currency: "DKK", + bestMatch: { + name: "Samsung 990 PRO 2TB", + price: 699, + store: "proshop", + }, + active: true, + }, + { + id: 2, + title: "32GB DDR5-6000 RAM", + category: "ram", + filters: { + capacity_gb: 32, + type: "DDR5", + brand: ["Corsair", "G.Skill"], + }, + targetPrice: 1000, + currency: "DKK", + bestMatch: { + name: "Corsair Vengeance 32GB DDR5-6000", + price: 899, + store: "proshop", + }, + active: true, + }, + { + id: 3, + title: "1TB NVMe SSD Budget", + category: "ssd", + filters: { + capacity_gb: 1000, + interface: "NVMe", + }, + targetPrice: 500, + currency: "DKK", + bestMatch: { + name: "Crucial P3 1TB", + price: 449, + store: "proshop", + }, + active: true, + }, + { + id: 4, + title: "16GB DDR4-3200 RAM", + category: "ram", + filters: { + capacity_gb: 16, + type: "DDR4", + }, + targetPrice: 400, + currency: "DKK", + bestMatch: null, + active: true, + }, + ]; + + const getFilterSummary = (filters: any) => { + const parts = []; + if (filters.capacity_gb) { + parts.push(filters.capacity_gb >= 1000 ? `${filters.capacity_gb / 1000}TB` : `${filters.capacity_gb}GB`); + } + if (filters.interface) { + parts.push(filters.interface); + } + if (filters.type) { + parts.push(filters.type); + } + if (filters.brand && filters.brand.length > 0) { + parts.push(`${filters.brand.join(", ")}`); + } + return parts.join(" • "); + }; + + return ( +
+ {profiles.map((profile) => { + const belowTarget = profile.bestMatch && profile.bestMatch.price <= profile.targetPrice; + + return ( +
+
+
+
+ +

{profile.title}

+ {belowTarget && ( + + + Deal Found + + )} + {profile.active && ( + Active + )} +
+
+ {profile.category} + + {getFilterSummary(profile.filters)} + + Target: {profile.targetPrice} {profile.currency} +
+
+ +
+ + {profile.bestMatch ? ( +
+
+
+

{profile.bestMatch.name}

+

+ {profile.bestMatch.store} +

+
+
+
+ {profile.bestMatch.price} {profile.currency} +
+ {belowTarget && ( +

+ {((profile.targetPrice - profile.bestMatch.price) / profile.targetPrice * 100).toFixed(1)}% below target +

+ )} +
+
+
+ ) : ( +
+

No matches found yet

+
+ )} +
+ ); + })} +
+ ); +}; diff --git a/src/index.css b/src/index.css index 4844bbd..8e63885 100644 --- a/src/index.css +++ b/src/index.css @@ -8,89 +8,95 @@ All colors MUST be HSL. @layer base { :root { - --background: 0 0% 100%; - --foreground: 222.2 84% 4.9%; + --background: 220 26% 14%; + --foreground: 210 40% 98%; - --card: 0 0% 100%; - --card-foreground: 222.2 84% 4.9%; + --card: 220 24% 18%; + --card-foreground: 210 40% 98%; - --popover: 0 0% 100%; - --popover-foreground: 222.2 84% 4.9%; + --popover: 220 24% 18%; + --popover-foreground: 210 40% 98%; - --primary: 222.2 47.4% 11.2%; - --primary-foreground: 210 40% 98%; + --primary: 217 91% 60%; + --primary-foreground: 222 47% 11%; - --secondary: 210 40% 96.1%; - --secondary-foreground: 222.2 47.4% 11.2%; + --secondary: 220 17% 25%; + --secondary-foreground: 210 40% 98%; - --muted: 210 40% 96.1%; - --muted-foreground: 215.4 16.3% 46.9%; + --muted: 220 17% 25%; + --muted-foreground: 215 20% 65%; - --accent: 210 40% 96.1%; - --accent-foreground: 222.2 47.4% 11.2%; + --accent: 263 70% 60%; + --accent-foreground: 210 40% 98%; - --destructive: 0 84.2% 60.2%; + --destructive: 0 84% 60%; --destructive-foreground: 210 40% 98%; + + --success: 142 76% 36%; + --success-foreground: 210 40% 98%; - --border: 214.3 31.8% 91.4%; - --input: 214.3 31.8% 91.4%; - --ring: 222.2 84% 4.9%; + --border: 220 17% 28%; + --input: 220 17% 28%; + --ring: 217 91% 60%; - --radius: 0.5rem; + --radius: 0.75rem; - --sidebar-background: 0 0% 98%; + --chart-1: 217 91% 60%; + --chart-2: 263 70% 60%; + --chart-3: 142 76% 36%; + --chart-4: 43 96% 56%; + --chart-5: 0 84% 60%; - --sidebar-foreground: 240 5.3% 26.1%; - - --sidebar-primary: 240 5.9% 10%; - - --sidebar-primary-foreground: 0 0% 98%; - - --sidebar-accent: 240 4.8% 95.9%; - - --sidebar-accent-foreground: 240 5.9% 10%; - - --sidebar-border: 220 13% 91%; - - --sidebar-ring: 217.2 91.2% 59.8%; + --sidebar-background: 220 26% 14%; + --sidebar-foreground: 210 40% 98%; + --sidebar-primary: 217 91% 60%; + --sidebar-primary-foreground: 222 47% 11%; + --sidebar-accent: 220 17% 25%; + --sidebar-accent-foreground: 210 40% 98%; + --sidebar-border: 220 17% 28%; + --sidebar-ring: 217 91% 60%; } .dark { - --background: 222.2 84% 4.9%; + --background: 220 26% 14%; --foreground: 210 40% 98%; - --card: 222.2 84% 4.9%; + --card: 220 24% 18%; --card-foreground: 210 40% 98%; - --popover: 222.2 84% 4.9%; + --popover: 220 24% 18%; --popover-foreground: 210 40% 98%; - --primary: 210 40% 98%; - --primary-foreground: 222.2 47.4% 11.2%; + --primary: 217 91% 60%; + --primary-foreground: 222 47% 11%; - --secondary: 217.2 32.6% 17.5%; + --secondary: 220 17% 25%; --secondary-foreground: 210 40% 98%; - --muted: 217.2 32.6% 17.5%; - --muted-foreground: 215 20.2% 65.1%; + --muted: 220 17% 25%; + --muted-foreground: 215 20% 65%; - --accent: 217.2 32.6% 17.5%; + --accent: 263 70% 60%; --accent-foreground: 210 40% 98%; - --destructive: 0 62.8% 30.6%; + --destructive: 0 84% 60%; --destructive-foreground: 210 40% 98%; + + --success: 142 76% 36%; + --success-foreground: 210 40% 98%; - --border: 217.2 32.6% 17.5%; - --input: 217.2 32.6% 17.5%; - --ring: 212.7 26.8% 83.9%; - --sidebar-background: 240 5.9% 10%; - --sidebar-foreground: 240 4.8% 95.9%; - --sidebar-primary: 224.3 76.3% 48%; - --sidebar-primary-foreground: 0 0% 100%; - --sidebar-accent: 240 3.7% 15.9%; - --sidebar-accent-foreground: 240 4.8% 95.9%; - --sidebar-border: 240 3.7% 15.9%; - --sidebar-ring: 217.2 91.2% 59.8%; + --border: 220 17% 28%; + --input: 220 17% 28%; + --ring: 217 91% 60%; + + --sidebar-background: 220 26% 14%; + --sidebar-foreground: 210 40% 98%; + --sidebar-primary: 217 91% 60%; + --sidebar-primary-foreground: 222 47% 11%; + --sidebar-accent: 220 17% 25%; + --sidebar-accent-foreground: 210 40% 98%; + --sidebar-border: 220 17% 28%; + --sidebar-ring: 217 91% 60%; } } diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index 7130b54..25d91e1 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -1,11 +1,178 @@ -// Update this page (the content is just a fallback if you fail to update the page) +import { useState } from "react"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { Button } from "@/components/ui/button"; +import { Badge } from "@/components/ui/badge"; +import { AlertCircle, TrendingDown, TrendingUp, Activity, Plus, Search, Package } from "lucide-react"; +import { ProductsList } from "@/components/ProductsList"; +import { SearchProfiles } from "@/components/SearchProfiles"; +import { AlertsList } from "@/components/AlertsList"; +import { PriceChart } from "@/components/PriceChart"; const Index = () => { + const [activeTab, setActiveTab] = useState("dashboard"); + + // Mock data - will be replaced with real data later + const stats = { + trackedProducts: 12, + activeProfiles: 5, + recentAlerts: 3, + avgPriceDrop: 8.5, + }; + return ( -
-
-

Welcome to Your Blank App

-

Start building your amazing project here!

+
+ {/* Header */} +
+
+
+
+
+ +
+
+

Hardware Price Tracker

+

Real-time monitoring & alerts

+
+
+ +
+
+
+ +
+ {/* Stats Overview */} +
+ + + Tracked Products + + + +
{stats.trackedProducts}
+

Active monitoring

+
+
+ + + + Search Profiles + + + +
{stats.activeProfiles}
+

Spec-based searches

+
+
+ + + + Recent Alerts + + + +
{stats.recentAlerts}
+

Last 24 hours

+
+
+ + + + Avg Price Drop + + + +
{stats.avgPriceDrop}%
+

Below target

+
+
+
+ + {/* Main Content Tabs */} + + + Dashboard + Products + Search Profiles + Alerts + + + +
+ + + Price Trends + Latest price movements for tracked products + + + + + + + + + + + Recent Alerts + + Price drops below target threshold + + + + + +
+ + + + Top Opportunities + Best deals currently available + + + + + +
+ + + + + Tracked Products + Direct URL monitoring for specific products + + + + + + + + + + + Search Profiles + Spec-based searches with brand filters + + + + + + + + + + + Alert History + All price drop notifications + + + + + + +
); diff --git a/tailwind.config.ts b/tailwind.config.ts index a1edb69..0b235b1 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -31,6 +31,10 @@ export default { DEFAULT: "hsl(var(--destructive))", foreground: "hsl(var(--destructive-foreground))", }, + success: { + DEFAULT: "hsl(var(--success))", + foreground: "hsl(var(--success-foreground))", + }, muted: { DEFAULT: "hsl(var(--muted))", foreground: "hsl(var(--muted-foreground))", @@ -57,6 +61,13 @@ export default { border: "hsl(var(--sidebar-border))", ring: "hsl(var(--sidebar-ring))", }, + chart: { + "1": "hsl(var(--chart-1))", + "2": "hsl(var(--chart-2))", + "3": "hsl(var(--chart-3))", + "4": "hsl(var(--chart-4))", + "5": "hsl(var(--chart-5))", + }, }, borderRadius: { lg: "var(--radius)",