diff --git a/frontend/src/app/terminal/intel/[tld]/page.tsx b/frontend/src/app/terminal/intel/[tld]/page.tsx index 49fc144..a720dc0 100644 --- a/frontend/src/app/terminal/intel/[tld]/page.tsx +++ b/frontend/src/app/terminal/intel/[tld]/page.tsx @@ -2,7 +2,7 @@ import { useEffect, useState, useMemo, useRef, useCallback, memo } from 'react' import { useParams, useRouter } from 'next/navigation' -import { TerminalLayout } from '@/components/TerminalLayout' +import { CommandCenterLayout } from '@/components/CommandCenterLayout' import { useStore } from '@/lib/store' import { api } from '@/lib/api' import { @@ -29,13 +29,14 @@ import { Lock, Sparkles, Diamond, - Activity + Activity, + Zap } from 'lucide-react' import Link from 'next/link' import clsx from 'clsx' // ============================================================================ -// TIER ACCESS LEVELS +// TYPES & HELPERS // ============================================================================ type UserTier = 'scout' | 'trader' | 'tycoon' @@ -50,87 +51,6 @@ function getTierLevel(tier: UserTier): number { } } -// ============================================================================ -// SHARED COMPONENTS (Synced with Overview) -// ============================================================================ - -const Tooltip = memo(({ children, content }: { children: React.ReactNode; content: string }) => ( -
- {children} -
- {content} -
-
-
-)) -Tooltip.displayName = 'Tooltip' - -// EXACT COPY OF STATCARD FROM INTEL PAGE (Modified for flexibility) -const StatCard = memo(({ - label, - value, - subValue, - icon: Icon, - highlight, - locked = false, - lockTooltip, - valueClassName -}: { - label: string - value: string | number - subValue?: string - icon: any - highlight?: boolean - locked?: boolean - lockTooltip?: string - valueClassName?: string -}) => ( -
- {/* Icon Top Right (Absolute) */} -
- -
- -
- {/* Label & Small Icon */} -
- - {label} -
- - {locked ? ( - -
- - -
-
- ) : ( -
-
- {value} -
- {subValue && {subValue}} -
- )} - - {highlight && ( -
- ● LIVE -
- )} -
-
-)) -StatCard.displayName = 'StatCard' - -// ============================================================================ -// TYPES & DATA -// ============================================================================ - interface TldDetails { tld: string type: string @@ -139,11 +59,7 @@ interface TldDetails { introduced: number trend: string trend_reason: string - pricing: { - avg: number - min: number - max: number - } + pricing: { avg: number; min: number; max: number } registrars: Array<{ name: string registration_price: number @@ -166,10 +82,7 @@ interface TldHistory { price_change_90d: number trend: string trend_reason: string - history: Array<{ - date: string - price: number - }> + history: Array<{ date: string; price: number }> } interface DomainCheckResult { @@ -192,7 +105,7 @@ const REGISTRAR_URLS: Record = { } // ============================================================================ -// SUB-COMPONENTS +// CHART COMPONENT // ============================================================================ function PriceChart({ @@ -207,8 +120,8 @@ function PriceChart({ if (data.length === 0) { return ( -
- +
+ No price history available
) @@ -258,25 +171,22 @@ function PriceChart({ {hoveredIndex !== null && points[hoveredIndex] && ( - - - + )} - {/* Hover Dot (HTML overlay to avoid SVG scaling distortion) */} {hoveredIndex !== null && points[hoveredIndex] && (
)} - {/* Chart Tooltip */} {hoveredIndex !== null && points[hoveredIndex] && (
- ${points[hoveredIndex].price.toFixed(2)} - {new Date(points[hoveredIndex].date).toLocaleDateString()} + ${points[hoveredIndex].price.toFixed(2)} + {new Date(points[hoveredIndex].date).toLocaleDateString()}
-
)}
) } -function LockedChartOverlay({ onUpgrade }: { onUpgrade: () => void }) { - return ( -
-
- -
-

Detailed History Locked

-

- Upgrade to Trader to access detailed price charts and historical data. -

- -
- ) -} - // ============================================================================ // MAIN PAGE // ============================================================================ -export default function CommandTldDetailPage() { +export default function TldDetailPage() { const params = useParams() const router = useRouter() const { fetchSubscription, subscription } = useStore() const tld = params.tld as string - // Determine user tier const userTier: UserTier = (subscription?.tier as UserTier) || 'scout' const tierLevel = getTierLevel(userTier) - // Feature access checks - const canAccessDetailPage = tierLevel >= 2 // Trader+ - const canSeeRenewal = tierLevel >= 2 // Trader+ - const canSeeFullHistory = tierLevel >= 3 // Tycoon only + const canAccessDetailPage = tierLevel >= 2 + const canSeeRenewal = tierLevel >= 2 + const canSeeFullHistory = tierLevel >= 3 - // Available chart periods based on tier const availablePeriods: ChartPeriod[] = useMemo(() => { if (tierLevel >= 3) return ['1M', '3M', '1Y', 'ALL'] - if (tierLevel >= 2) return ['1Y'] // Trader gets only 1Y - return [] // Scout gets no chart + if (tierLevel >= 2) return ['1Y'] + return [] }, [tierLevel]) const [details, setDetails] = useState(null) @@ -360,9 +244,7 @@ export default function CommandTldDetailPage() { useEffect(() => { fetchSubscription() - if (tld) { - loadData() - } + if (tld) loadData() }, [tld, fetchSubscription]) const loadData = async () => { @@ -377,7 +259,6 @@ export default function CommandTldDetailPage() { const sortedRegistrars = [...(compareData.registrars || [])].sort((a, b) => a.registration_price - b.registration_price ) - const tldFromOverview = overviewData?.tlds?.[0] setDetails({ @@ -415,17 +296,14 @@ export default function CommandTldDetailPage() { const filteredHistory = useMemo(() => { if (!history?.history) return [] - const now = new Date() let cutoffDays = 365 - switch (chartPeriod) { case '1M': cutoffDays = 30; break case '3M': cutoffDays = 90; break case '1Y': cutoffDays = 365; break case 'ALL': cutoffDays = 9999; break } - const cutoff = new Date(now.getTime() - cutoffDays * 24 * 60 * 60 * 1000) return history.history.filter(h => new Date(h.date) >= cutoff) }, [history, chartPeriod]) @@ -442,10 +320,8 @@ export default function CommandTldDetailPage() { const handleDomainCheck = async () => { if (!domainSearch.trim()) return - setCheckingDomain(true) setDomainResult(null) - try { const domain = domainSearch.includes('.') ? domainSearch : `${domainSearch}.${tld}` const result = await api.checkDomain(domain, false) @@ -475,404 +351,406 @@ export default function CommandTldDetailPage() { 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, - } + return { registration: cheapest.registration_price, renewal: cheapest.renewal_price, ratio, isTrap: ratio > 2 } } const renewalInfo = getRenewalInfo() - const getRiskBadge = () => { - if (!details) return null - const level = details.risk_level - const reason = details.risk_reason - return ( - - - - {level} Risk - - - ) - } - - const handleUpgrade = useCallback(() => { - router.push('/pricing') - }, [router]) - if (loading) { return ( - +
- -
-
+ +
+ ) } if (error || !details) { return ( - -
- -

TLD Not Found

-

The extension .{tld} is not currently tracked.

- - Back to Intelligence - -
-
+ +
+ +

TLD Not Found

+

The extension .{tld} is not currently tracked.

+ + Back to Intel + +
+
) } return ( - -
- - {/* Ambient Background Glow (Consistent with Overview) */} -
-
-
-
- -
- - {/* Header Section */} -
-
- {/* Breadcrumb */} - - -
-
-
-
-

- .{details.tld} -

- {getRiskBadge()} -
-

- {details.description} -

-
-
-
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* HEADER */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+
+ {/* Breadcrumb */} + -
- {/* Tier Badge */} -
- - {userTier === 'tycoon' ? 'Tycoon Access' : userTier === 'trader' ? 'Trader Access' : 'Scout Access'} +
+
+
+
+

+ .{details.tld} +

+ {/* Risk Badge */} + + {details.risk_level} risk + +
+

{details.description}

- - {/* Metric Grid */} -
- - - 0 ? '+' : ''}${details.price_change_1y.toFixed(0)}%`} - subValue="Volatility" - icon={details.price_change_1y > 0 ? TrendingUp : TrendingDown} - valueClassName={ - details.price_change_1y > 5 ? "text-orange-400" : - details.price_change_1y < -5 ? "text-emerald-400" : - "text-zinc-400" - } - /> - 0 ? '+' : ''}${details.price_change_3y.toFixed(0)}%` : '—'} - subValue={canSeeFullHistory ? "Long-term" : undefined} - icon={BarChart3} - locked={!canSeeFullHistory} - lockTooltip="Upgrade to Tycoon for 3-year trends" - valueClassName={ - canSeeFullHistory && details.price_change_3y > 10 ? "text-orange-400" : - canSeeFullHistory && details.price_change_3y < -10 ? "text-emerald-400" : - "text-zinc-400" - } - /> -
- -
- - {/* Left Column: Chart & Info */} -
- - {/* Price History Chart */} -
-
- -
- - {/* Lock overlay for Scout users */} - {!canAccessDetailPage && } - -
-
-

- - Price History -

-

Historical registration price trends

-
-
- {(['1M', '3M', '1Y', 'ALL'] as ChartPeriod[]).map((period) => { - const isAvailable = availablePeriods.includes(period) - const isActive = chartPeriod === period && isAvailable - return ( - - - - ) - })} -
-
-
- -
- -
-
-
High
-
${chartStats.high.toFixed(2)}
-
-
-
Average
-
${chartStats.avg.toFixed(2)}
-
-
-
Low
-
${chartStats.low.toFixed(2)}
-
-
-
- - {/* Quick Check Bar */} -
-
-
-
-

- - Check Availability -

-

Instantly check if your desired .{details.tld} domain is available.

-
- -
-
- setDomainSearch(e.target.value)} - onKeyDown={(e) => e.key === 'Enter' && handleDomainCheck()} - placeholder={`example.${details.tld}`} - className="w-full h-12 bg-black/50 border border-white/10 rounded-lg pl-4 pr-4 text-white placeholder:text-zinc-600 focus:outline-none focus:border-emerald-500/50 focus:ring-1 focus:ring-emerald-500/50 transition-all font-mono" - /> -
- -
-
- - {/* Check Result */} - {domainResult && ( -
+
-
- {domainResult.is_available ? ( -
- ) : ( -
+ + {userTier} +
+
+
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* STATS GRID */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+ {/* Registration */} +
+
+ + Registration +
+
${details.pricing.min.toFixed(2)}
+
at {details.cheapest_registrar}
+
+ + {/* Renewal */} +
+
+ + Renewal +
+ {canSeeRenewal ? ( + <> +
+ ${details.min_renewal_price.toFixed(2)} +
+ {renewalInfo?.isTrap && ( +
{renewalInfo.ratio.toFixed(1)}x markup
)} + + ) : ( +
+ + +
+ )} +
+ + {/* 1Y Trend */} +
+
+ {details.price_change_1y > 0 ? : } + 1Y Trend +
+
5 ? "text-orange-400" : + details.price_change_1y < -5 ? "text-accent" : + "text-white/40" + )}> + {details.price_change_1y > 0 ? '+' : ''}{details.price_change_1y.toFixed(0)}% +
+
+ + {/* 3Y Trend */} +
+
+ + 3Y Trend +
+ {canSeeFullHistory ? ( +
10 ? "text-orange-400" : + details.price_change_3y < -10 ? "text-accent" : + "text-white/40" + )}> + {details.price_change_3y > 0 ? '+' : ''}{details.price_change_3y.toFixed(0)}% +
+ ) : ( +
+ + +
+ )} +
+
+
+ + {/* ═══════════════════════════════════════════════════════════════════════ */} + {/* MAIN CONTENT */} + {/* ═══════════════════════════════════════════════════════════════════════ */} +
+
+ + {/* Left Column: Chart + Search */} +
+ + {/* Price Chart */} +
+ {/* Lock overlay for Scout */} + {!canAccessDetailPage && ( +
+
+ +
+

Charts Locked

+

+ Upgrade to Trader for detailed price history. +

+ + + Unlock + +
+ )} + +
-
{domainResult.domain}
-
- {domainResult.is_available ? 'Available for registration' : 'Already Registered'} -
+

Price History

+
+
+ {(['1M', '3M', '1Y', 'ALL'] as ChartPeriod[]).map((period) => { + const isAvailable = availablePeriods.includes(period) + const isActive = chartPeriod === period && isAvailable + return ( + + ) + })}
- {domainResult.is_available && ( - - Buy at {details.cheapest_registrar} - - )} -
-
- )} -
- - {/* TLD Info Cards */} -
-
-
- - Type +
+ +
+ +
+
+
High
+
${chartStats.high.toFixed(2)}
+
+
+
Average
+
${chartStats.avg.toFixed(2)}
+
+
+
Low
+
${chartStats.low.toFixed(2)}
+
-
{details.type}
-
-
- - Registry -
-
{details.registry}
-
+ + {/* Domain Check */} +
+
+
+

+ + Check Availability +

+

Check if your .{details.tld} domain is available

-
- - {/* Right Column: Registrars Table */} -
-
-

Registrar Prices

-

Live comparison sorted by price

+
+ setDomainSearch(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleDomainCheck()} + placeholder={`example.${details.tld}`} + className="flex-1 h-10 bg-white/5 border border-white/10 px-4 text-white placeholder:text-white/25 outline-none focus:border-accent/50 font-mono text-sm" + /> + +
- -
- - - - - - - - - - - {details.registrars.map((registrar, idx) => { - const hasRenewalTrap = registrar.renewal_price / registrar.registration_price > 1.5 - const isBest = idx === 0 && !hasRenewalTrap - - return ( - - - - - - - ) - })} - -
RegistrarReg - {canSeeRenewal ? 'Renew' : ( - - - Renew - - - )} -
-
{registrar.name}
- {isBest && Best Value} - {idx === 0 && hasRenewalTrap && canSeeRenewal && Renewal Trap} -
-
- ${registrar.registration_price.toFixed(2)} -
-
- {canSeeRenewal ? ( -
- ${registrar.renewal_price.toFixed(2)} -
- ) : ( -
- )} -
- - - -
-
- - {/* Upgrade CTA for Scout users */} - {userTier === 'scout' && ( -
- - - Upgrade to see renewal prices - -
- )} + + {domainResult && ( +
+
+
+ {domainResult.is_available ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+
{domainResult.domain}
+
+ {domainResult.is_available ? 'Available' : 'Registered'} +
+
+
+ + {domainResult.is_available && ( + + Buy at {details.cheapest_registrar} + + + )} +
+
+ )}
+ {/* Info Cards */} +
+
+
+ + Type +
+
{details.type}
+
+
+
+ + Registry +
+
{details.registry}
+
+
+ {/* Right Column: Registrars */} +
+
+

Registrar Prices

+
+ +
+ {details.registrars.map((registrar, idx) => { + const hasRenewalTrap = registrar.renewal_price / registrar.registration_price > 1.5 + const isBest = idx === 0 && !hasRenewalTrap + + return ( +
+
+
+
{registrar.name}
+ {isBest && Best Value} + {idx === 0 && hasRenewalTrap && canSeeRenewal && ( + Renewal Trap + )} +
+
+
+ ${registrar.registration_price.toFixed(2)} +
+ {canSeeRenewal && ( +
+ ${registrar.renewal_price.toFixed(2)}/yr +
+ )} +
+
+ + Visit + + +
+ ) + })} +
+ + {userTier === 'scout' && ( +
+ + + Upgrade for renewal prices + +
+ )} +
-
- +
+
) }