From a64c172f9c25a980bd49642ff1ba5432f3fba93e Mon Sep 17 00:00:00 2001 From: "yves.gugger" Date: Wed, 10 Dec 2025 15:55:38 +0100 Subject: [PATCH] refactor: TLD Detail pages - remove alerts, add all table data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit REMOVED: - Alert/Notify button from both pages - Bell icon and handleToggleAlert functions - alertEnabled, alertLoading state ADDED (matching table columns): - Buy Price (1y) - was already there - Renewal (1y) with trap warning - 1y Change with color coding - 3y Change with color coding - Risk Assessment with badge (dot + reason text) COMMAND CENTER (/command/pricing/[tld]): - StatCards: Buy, Renew, 1y Change, 3y Change - Risk Assessment section with badge - Renewal Trap warning when ratio > 2x - Real chart data from history.history API PUBLIC (/tld-pricing/[tld]): - Same stats in Quick Stats grid - Risk Assessment for authenticated users - Shimmer placeholders for non-authenticated - Real chart data from history.history API Both pages now show ALL info from the overview table: ✅ TLD name ✅ Trend (chart + badge) ✅ Buy (1y) ✅ Renew (1y) with trap ✅ 1y Change ✅ 3y Change ✅ Risk Level + Reason --- .../src/app/command/pricing/[tld]/page.tsx | 132 ++++++------ frontend/src/app/tld-pricing/[tld]/page.tsx | 200 ++++++++++-------- 2 files changed, 177 insertions(+), 155 deletions(-) diff --git a/frontend/src/app/command/pricing/[tld]/page.tsx b/frontend/src/app/command/pricing/[tld]/page.tsx index 0e076f7..f46e031 100644 --- a/frontend/src/app/command/pricing/[tld]/page.tsx +++ b/frontend/src/app/command/pricing/[tld]/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState, useMemo, useRef } from 'react' -import { useParams, useRouter } from 'next/navigation' +import { useParams } from 'next/navigation' import { CommandCenterLayout } from '@/components/CommandCenterLayout' import { PageContainer, StatCard } from '@/components/PremiumTable' import { useStore } from '@/lib/store' @@ -15,19 +15,15 @@ import { Globe, Building, ExternalLink, - Bell, Search, ChevronRight, - Sparkles, Check, X, RefreshCw, - Clock, - Shield, - Zap, AlertTriangle, DollarSign, BarChart3, + Shield, } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' @@ -52,6 +48,12 @@ interface TldDetails { transfer_price: number }> cheapest_registrar: string + // New fields from table + min_renewal_price: number + price_change_1y: number + price_change_3y: number + risk_level: 'low' | 'medium' | 'high' + risk_reason: string } interface TldHistory { @@ -90,7 +92,7 @@ const REGISTRAR_URLS: Record = { type ChartPeriod = '1M' | '3M' | '1Y' | 'ALL' -// Premium Chart Component +// Premium Chart Component with real data function PriceChart({ data, chartStats, @@ -187,8 +189,7 @@ function PriceChart({ export default function CommandTldDetailPage() { const params = useParams() - const router = useRouter() - const { subscription, fetchSubscription } = useStore() + const { fetchSubscription } = useStore() const tld = params.tld as string const [details, setDetails] = useState(null) @@ -199,43 +200,20 @@ export default function CommandTldDetailPage() { const [domainSearch, setDomainSearch] = useState('') const [checkingDomain, setCheckingDomain] = useState(false) const [domainResult, setDomainResult] = useState(null) - const [alertEnabled, setAlertEnabled] = useState(false) - const [alertLoading, setAlertLoading] = useState(false) useEffect(() => { fetchSubscription() if (tld) { loadData() - loadAlertStatus() } }, [tld, fetchSubscription]) - const loadAlertStatus = async () => { - try { - const status = await api.getPriceAlertStatus(tld) - setAlertEnabled(status.has_alert && status.is_active) - } catch (err) { - // Ignore - } - } - - const handleToggleAlert = async () => { - setAlertLoading(true) - try { - const result = await api.togglePriceAlert(tld) - setAlertEnabled(result.is_active) - } catch (err) { - console.error('Failed to toggle alert:', err) - } finally { - setAlertLoading(false) - } - } - const loadData = async () => { try { - const [historyData, compareData] = await Promise.all([ + const [historyData, compareData, overviewData] = await Promise.all([ api.getTldHistory(tld, 365), api.getTldCompare(tld), + api.getTldOverview(1, 0, 'popularity', tld), // Get the specific TLD data ]) if (historyData && compareData) { @@ -243,6 +221,9 @@ export default function CommandTldDetailPage() { a.registration_price - b.registration_price ) + // Get additional data from overview API (1y, 3y change, risk) + const tldFromOverview = overviewData?.tlds?.[0] + setDetails({ tld: compareData.tld || tld, type: compareData.type || 'generic', @@ -258,6 +239,12 @@ export default function CommandTldDetailPage() { }, registrars: sortedRegistrars, cheapest_registrar: compareData.cheapest_registrar || sortedRegistrars[0]?.name || 'N/A', + // New fields from overview + min_renewal_price: tldFromOverview?.min_renewal_price || sortedRegistrars[0]?.renewal_price || 0, + price_change_1y: tldFromOverview?.price_change_1y || 0, + price_change_3y: tldFromOverview?.price_change_3y || 0, + risk_level: tldFromOverview?.risk_level || 'low', + risk_reason: tldFromOverview?.risk_reason || 'Stable', }) setHistory(historyData) } else { @@ -344,12 +331,27 @@ export default function CommandTldDetailPage() { const renewalInfo = getRenewalInfo() - const getTrendIcon = (trend: string) => { - switch (trend) { - case 'up': return - case 'down': return - default: return - } + // Risk badge component + const getRiskBadge = () => { + if (!details) return null + const level = details.risk_level + const reason = details.risk_reason + return ( + + + {reason} + + ) } if (loading) { @@ -391,21 +393,6 @@ export default function CommandTldDetailPage() { - - {alertEnabled ? 'Alert On' : 'Set Alert'} - - } > {/* Breadcrumb */} @@ -417,34 +404,42 @@ export default function CommandTldDetailPage() { .{details.tld} - {/* Stats Grid */} + {/* Stats Grid - All info from table */}
0 ? '+' : ''}${history.price_change_7d.toFixed(1)}%` : '—'} - icon={BarChart3} + title="1y Change" + value={`${details.price_change_1y > 0 ? '+' : ''}${details.price_change_1y.toFixed(0)}%`} + icon={details.price_change_1y > 0 ? TrendingUp : details.price_change_1y < 0 ? TrendingDown : Minus} /> 0 ? '+' : ''}${details.price_change_3y.toFixed(0)}%`} + icon={BarChart3} />
+ {/* Risk Level */} +
+ +
+

Risk Assessment

+

Based on renewal ratio, price volatility, and market trends

+
+ {getRiskBadge()} +
+ {/* Renewal Trap Warning */} {renewalInfo?.isTrap && (
@@ -675,4 +670,3 @@ export default function CommandTldDetailPage() { ) } - diff --git a/frontend/src/app/tld-pricing/[tld]/page.tsx b/frontend/src/app/tld-pricing/[tld]/page.tsx index d701b24..538477d 100644 --- a/frontend/src/app/tld-pricing/[tld]/page.tsx +++ b/frontend/src/app/tld-pricing/[tld]/page.tsx @@ -1,7 +1,7 @@ 'use client' import { useEffect, useState, useMemo, useRef } from 'react' -import { useParams, useRouter } from 'next/navigation' +import { useParams } from 'next/navigation' import { Header } from '@/components/Header' import { Footer } from '@/components/Footer' import { useStore } from '@/lib/store' @@ -15,10 +15,8 @@ import { Globe, Building, ExternalLink, - Bell, Search, ChevronRight, - Sparkles, Check, X, Lock, @@ -26,6 +24,7 @@ import { Clock, Shield, Zap, + AlertTriangle, } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' @@ -50,6 +49,12 @@ interface TldDetails { transfer_price: number }> cheapest_registrar: string + // New fields from table + min_renewal_price: number + price_change_1y: number + price_change_3y: number + risk_level: 'low' | 'medium' | 'high' + risk_reason: string } interface TldHistory { @@ -79,8 +84,7 @@ interface DomainCheckResult { expiration_date?: string | null } -// Registrar URLs with affiliate parameters -// Note: Replace REF_CODE with actual affiliate IDs when available +// Registrar URLs const REGISTRAR_URLS: Record = { 'Namecheap': 'https://www.namecheap.com/domains/registration/results/?domain=', 'Porkbun': 'https://porkbun.com/checkout/search?q=', @@ -120,7 +124,7 @@ function Shimmer({ className }: { className?: string }) { ) } -// Premium Chart Component +// Premium Chart Component with real data function PriceChart({ data, isAuthenticated, @@ -294,7 +298,7 @@ function PriceChart({ ) } -// Domain Check Result Card (like landing page) +// Domain Check Result Card function DomainResultCard({ result, tld, @@ -390,7 +394,6 @@ function DomainResultCard({ export default function TldDetailPage() { const params = useParams() - const router = useRouter() const { isAuthenticated, checkAuth, isLoading: authLoading, subscription, fetchSubscription } = useStore() const tld = params.tld as string @@ -406,8 +409,6 @@ export default function TldDetailPage() { const [domainSearch, setDomainSearch] = useState('') const [checkingDomain, setCheckingDomain] = useState(false) const [domainResult, setDomainResult] = useState(null) - const [alertEnabled, setAlertEnabled] = useState(false) - const [alertLoading, setAlertLoading] = useState(false) useEffect(() => { checkAuth() @@ -418,53 +419,25 @@ export default function TldDetailPage() { if (tld) { loadData() loadRelatedTlds() - loadAlertStatus() } }, [tld]) - // Load alert status for this TLD - const loadAlertStatus = async () => { - try { - const status = await api.getPriceAlertStatus(tld) - setAlertEnabled(status.has_alert && status.is_active) - } catch (err) { - // Ignore - user may not be logged in - } - } - - // Toggle price alert - const handleToggleAlert = async () => { - if (!isAuthenticated) { - // Redirect to login - window.location.href = `/login?redirect=/tld-pricing/${tld}` - return - } - - setAlertLoading(true) - try { - const result = await api.togglePriceAlert(tld) - setAlertEnabled(result.is_active) - } catch (err: any) { - console.error('Failed to toggle alert:', err) - } finally { - setAlertLoading(false) - } - } - const loadData = async () => { try { - const [historyData, compareData] = await Promise.all([ + const [historyData, compareData, overviewData] = await Promise.all([ api.getTldHistory(tld, 365), api.getTldCompare(tld), + api.getTldOverview(1, 0, 'popularity', tld), ]) if (historyData && compareData) { - // Sort registrars by price for display const sortedRegistrars = [...(compareData.registrars || [])].sort((a, b) => a.registration_price - b.registration_price ) - // Use API data directly for consistency with overview table + // Get additional data from overview API + const tldFromOverview = overviewData?.tlds?.[0] + setDetails({ tld: compareData.tld || tld, type: compareData.type || 'generic', @@ -474,13 +447,18 @@ export default function TldDetailPage() { trend: historyData.trend || 'stable', trend_reason: historyData.trend_reason || 'Price tracking available', pricing: { - // Use price_range from API for consistency with overview avg: compareData.price_range?.avg || historyData.current_price || 0, min: compareData.price_range?.min || historyData.current_price || 0, max: compareData.price_range?.max || historyData.current_price || 0, }, registrars: sortedRegistrars, cheapest_registrar: compareData.cheapest_registrar || sortedRegistrars[0]?.name || 'N/A', + // New fields from overview + min_renewal_price: tldFromOverview?.min_renewal_price || sortedRegistrars[0]?.renewal_price || 0, + price_change_1y: tldFromOverview?.price_change_1y || 0, + price_change_3y: tldFromOverview?.price_change_3y || 0, + risk_level: tldFromOverview?.risk_level || 'low', + risk_reason: tldFromOverview?.risk_reason || 'Stable', }) setHistory(historyData) } else { @@ -580,6 +558,42 @@ export default function TldDetailPage() { } }, [details]) + // Renewal trap info + const renewalInfo = useMemo(() => { + if (!details?.registrars?.length) return null + const cheapest = details.registrars[0] + const ratio = cheapest.renewal_price / cheapest.registration_price + return { + registration: cheapest.registration_price, + renewal: cheapest.renewal_price, + ratio, + isTrap: ratio > 2, + } + }, [details]) + + // Risk badge component + const getRiskBadge = () => { + if (!details) return null + const level = details.risk_level + const reason = details.risk_reason + return ( + + + {reason} + + ) + } + const getTrendIcon = (trend: string) => { switch (trend) { case 'up': return @@ -674,50 +688,73 @@ export default function TldDetailPage() {

{details.description}

{details.trend_reason}

- {/* Quick Stats - Only for authenticated */} + {/* Quick Stats - All data from table */}
-

Average

+

Buy (1y)

{isAuthenticated ? ( -

${details.pricing.avg.toFixed(2)}

+

${details.pricing.min.toFixed(2)}

) : ( )}
-

Range

+

Renew (1y)

{isAuthenticated ? ( -

- ${details.pricing.min.toFixed(0)}–${details.pricing.max.toFixed(0)} -

+
+

+ ${details.min_renewal_price.toFixed(2)} +

+ {renewalInfo?.isTrap && ( + + )} +
) : ( )}
-

30d Change

- {isAuthenticated && history ? ( +

1y Change

+ {isAuthenticated ? (

0 ? "text-orange-400" : - history.price_change_30d < 0 ? "text-accent" : + details.price_change_1y > 0 ? "text-orange-400" : + details.price_change_1y < 0 ? "text-accent" : "text-foreground" )}> - {history.price_change_30d > 0 ? '+' : ''}{history.price_change_30d.toFixed(1)}% + {details.price_change_1y > 0 ? '+' : ''}{details.price_change_1y.toFixed(0)}%

) : ( )}
-

Registrars

+

3y Change

{isAuthenticated ? ( -

{details.registrars.length}

+

0 ? "text-orange-400" : + details.price_change_3y < 0 ? "text-accent" : + "text-foreground" + )}> + {details.price_change_3y > 0 ? '+' : ''}{details.price_change_3y.toFixed(0)}% +

) : ( - + )}
+ + {/* Risk Assessment */} + {isAuthenticated && ( +
+ +
+

Risk Assessment

+
+ {getRiskBadge()} +
+ )}
{/* Right: Price Card */} @@ -745,35 +782,12 @@ export default function TldDetailPage() { Register Domain - - {savings && savings.amount > 0.5 && (
- +

Save ${savings.amount.toFixed(2)}/yr vs {savings.expensiveName}

@@ -798,6 +812,20 @@ export default function TldDetailPage() {
+ {/* Renewal Trap Warning */} + {isAuthenticated && renewalInfo?.isTrap && ( +
+ +
+

Renewal Trap Detected

+

+ The renewal price (${renewalInfo.renewal.toFixed(2)}) is {renewalInfo.ratio.toFixed(1)}x higher than the registration price (${renewalInfo.registration.toFixed(2)}). + Consider the total cost of ownership before registering. +

+
+
+ )} + {/* Price Chart */}
@@ -982,7 +1010,7 @@ export default function TldDetailPage() { ${registrar.renewal_price.toFixed(2)} {registrar.renewal_price > registrar.registration_price * 1.5 && ( - + )} @@ -1093,10 +1121,10 @@ export default function TldDetailPage() { Monitor specific domains and get instant notifications when they become available.

- {isAuthenticated ? 'Go to Dashboard' : 'Start Monitoring Free'} + {isAuthenticated ? 'Go to Command Center' : 'Start Monitoring Free'}