From f4c355b2cf23d7a6ceb0e153d2a3f8634e0487ba Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Wed, 17 Dec 2025 17:19:28 +0100 Subject: [PATCH] Redesign AnalyzePanel + VisionSection to match Hunt page style --- .../src/components/analyze/AnalyzePanel.tsx | 478 +++++++++++------- .../src/components/analyze/VisionSection.tsx | 354 ++++++------- 2 files changed, 478 insertions(+), 354 deletions(-) diff --git a/frontend/src/components/analyze/AnalyzePanel.tsx b/frontend/src/components/analyze/AnalyzePanel.tsx index ee91b0f..37f5e1d 100644 --- a/frontend/src/components/analyze/AnalyzePanel.tsx +++ b/frontend/src/components/analyze/AnalyzePanel.tsx @@ -14,15 +14,17 @@ import { Check, Zap, Globe, - Calendar, - Link2, - Radio, - Eye, ChevronDown, - ChevronUp, + ChevronRight, CheckCircle2, XCircle, Sparkles, + Info, + Clock, + Link2, + Search, + Eye, + Filter, } from 'lucide-react' import { api } from '@/lib/api' import { useAnalyzePanelStore } from '@/lib/analyze-store' @@ -36,47 +38,66 @@ import { VisionSection } from '@/components/analyze/VisionSection' function getStatusColor(status: string) { switch (status) { case 'pass': - return { bg: 'bg-accent/20', text: 'text-accent', border: 'border-accent/40', icon: CheckCircle2 } + return { bg: 'bg-accent/10', text: 'text-accent', border: 'border-accent/30', icon: CheckCircle2 } case 'warn': - return { bg: 'bg-amber-400/20', text: 'text-amber-300', border: 'border-amber-400/40', icon: AlertTriangle } + return { bg: 'bg-amber-400/10', text: 'text-amber-400', border: 'border-amber-400/30', icon: AlertTriangle } case 'fail': - return { bg: 'bg-red-500/20', text: 'text-red-400', border: 'border-red-500/40', icon: XCircle } + return { bg: 'bg-rose-500/10', text: 'text-rose-400', border: 'border-rose-500/30', icon: XCircle } default: - return { bg: 'bg-white/10', text: 'text-white/50', border: 'border-white/20', icon: null } + return { bg: 'bg-white/5', text: 'text-white/40', border: 'border-white/10', icon: null } } } -function getSectionIcon(key: string) { +function getSectionConfig(key: string) { switch (key) { case 'authority': - return Shield + return { + icon: Shield, + color: 'text-blue-400', + bg: 'bg-blue-500/10', + border: 'border-blue-500/20', + description: 'Age, backlinks, and trust signals' + } case 'market': - return TrendingUp + return { + icon: TrendingUp, + color: 'text-emerald-400', + bg: 'bg-emerald-500/10', + border: 'border-emerald-500/20', + description: 'Search volume, CPC, and TLD availability' + } case 'risk': - return AlertTriangle + return { + icon: AlertTriangle, + color: 'text-amber-400', + bg: 'bg-amber-500/10', + border: 'border-amber-500/20', + description: 'Trademark, blacklist, and archive checks' + } case 'value': - return DollarSign + return { + icon: DollarSign, + color: 'text-violet-400', + bg: 'bg-violet-500/10', + border: 'border-violet-500/20', + description: 'Estimated value and comparable sales' + } case 'vision': - return Sparkles + return { + icon: Sparkles, + color: 'text-accent', + bg: 'bg-accent/10', + border: 'border-accent/20', + description: 'AI-powered business concept and buyer analysis' + } default: - return Globe - } -} - -function getSectionColor(key: string) { - switch (key) { - case 'authority': - return { text: 'text-blue-400', bg: 'bg-blue-500/10', border: 'border-blue-500/30' } - case 'market': - return { text: 'text-emerald-400', bg: 'bg-emerald-500/10', border: 'border-emerald-500/30' } - case 'risk': - return { text: 'text-amber-400', bg: 'bg-amber-500/10', border: 'border-amber-500/30' } - case 'value': - return { text: 'text-violet-400', bg: 'bg-violet-500/10', border: 'border-violet-500/30' } - case 'vision': - return { text: 'text-accent', bg: 'bg-accent/10', border: 'border-accent/30' } - default: - return { text: 'text-white/60', bg: 'bg-white/5', border: 'border-white/20' } + return { + icon: Globe, + color: 'text-white/50', + bg: 'bg-white/5', + border: 'border-white/10', + description: '' + } } } @@ -102,6 +123,24 @@ function isMatrix(item: AnalyzeItem) { return item.key === 'tld_matrix' && Array.isArray(item.value) } +function getItemTooltip(key: string): string { + const tooltips: Record = { + domain_age: 'How long the domain has been registered. Older = more authority.', + backlinks: 'Number of external websites linking to this domain.', + trust_flow: 'Quality score of backlinks (0-100). Higher = more trusted.', + radio_test: 'How easy is the domain to spell when heard verbally.', + search_volume: 'Monthly Google searches for the main keyword.', + cpc: 'Cost-per-click for ads on this keyword. Higher = more commercial value.', + tld_matrix: 'Availability of this name across popular TLDs.', + trademark: 'Potential trademark conflicts. Clear = safe to use.', + blacklist: 'Whether the domain appears on spam/malware lists.', + archive: 'Wayback Machine history. Shows previous usage.', + estimated_value: 'AI-estimated market value based on comparables.', + comps: 'Similar domains that have sold recently.', + } + return tooltips[key] || '' +} + // ============================================================================ // COMPONENT // ============================================================================ @@ -187,10 +226,12 @@ export function AnalyzePanel() { .sort((a, b) => order.indexOf(a.key) - order.indexOf(b.key)) .filter((s) => sectionVisibility[s.key] !== false) - // Append VISION (Terminal-only LLM augmentation) - if (sectionVisibility.vision === false) return sorted - const visionSection: AnalyzeSection = { key: 'vision', title: 'VISION', items: [] } - return [...sorted, visionSection] + // Append VISION section + if (sectionVisibility.vision !== false) { + const visionSection: AnalyzeSection = { key: 'vision', title: 'VISION', items: [] } + return [...sorted, visionSection] + } + return sorted }, [data, sectionVisibility]) // Calculate overall score @@ -217,28 +258,31 @@ export function AnalyzePanel() { return (
{/* Backdrop */} -
+
- {/* Panel - WIDER & MORE READABLE */} -
+ {/* Panel */} +
{/* Header */} -
+
{/* Top Bar */} -
-
-
- +
+
+
+
-
-
Domain Analysis
-
+
+
Domain Analysis
+
{headerDomain}
-
+
- +
- {/* Score Bar - LARGER */} + {/* Score Bar */} {overallScore && !loading && ( -
-
+
+
= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-red-400" + "text-3xl font-bold font-mono", + overallScore.score >= 70 ? "text-accent" : overallScore.score >= 40 ? "text-amber-400" : "text-rose-400" )}> {overallScore.score}
-
-
Overall Score
-
+
+
Health Score
+
-
- {overallScore.pass} - {overallScore.warn} - {overallScore.fail} +
+ + {overallScore.pass} + + + {overallScore.warn} + + + {overallScore.fail} +
)} - {/* Mode Toggle */} -
+ {/* Controls */} +
{data?.cached && ( - - ⚡ Cached + + + Cached )}
- {/* Body - BETTER SPACING */} + {/* Body */}
{loading ? ( -
+
- -
Analyzing domain...
+ +
Analyzing domain...
) : error ? ( -
-
-
Analysis Failed
-
{error}
+
+
+
Analysis Failed
+
{error}
) : !data ? ( -
-
No data available
+
+
No data available
) : ( -
+
{visibleSections.map((section) => { - const SectionIcon = getSectionIcon(section.key) - const sectionStyle = getSectionColor(section.key) + const config = getSectionConfig(section.key) + const SectionIcon = config.icon const isExpanded = expandedSections[section.key] !== false return ( -
- {/* Section Header - LARGER */} +
+ {/* Section Header */} - {/* Section Items - BETTER CONTRAST */} + {/* Section Content */} {isExpanded && ( -
+
{section.key === 'vision' ? ( -
+
- ) : section.items.map((item) => { - const statusStyle = getStatusColor(item.status) - const StatusIcon = statusStyle.icon + ) : ( +
+ {section.items.map((item) => { + const statusStyle = getStatusColor(item.status) + const StatusIcon = statusStyle.icon + const tooltip = getItemTooltip(item.key) - return ( -
-
- {/* Status Indicator - LARGER */} -
- {StatusIcon && } -
+ return ( +
+
+ {/* Status Indicator */} +
+ {StatusIcon && } +
- {/* Content - BETTER READABILITY */} -
-
- - {item.label} - - - {item.source} - -
- - {/* Value - LARGER TEXT */} -
- {isMatrix(item) ? ( -
- {(item.value as any[]).slice(0, 12).map((row: any) => ( -
- {String(row.domain)} - {row.status === 'available' && } + {/* Content */} +
+
+
+ + {item.label} + + {tooltip && ( + + )} +
+ + {item.source} + +
+ + {/* Value */} +
+ {isMatrix(item) ? ( +
+ {(item.value as any[]).slice(0, 12).map((row: any) => ( +
+ .{String(row.domain).split('.').pop()} + {row.status === 'available' && } +
+ ))}
- ))} + ) : ( +
+ {formatValue(item.value)} +
+ )}
- ) : ( -
- {formatValue(item.value)} -
- )} -
- {/* Details Toggle */} - {item.details && Object.keys(item.details).length > 0 && ( -
- - View raw details - -
-                                        {JSON.stringify(item.details, null, 2)}
-                                      
-
- )} + {/* Details Toggle */} + {item.details && Object.keys(item.details).length > 0 && ( +
+ + Raw data + +
+                                            {JSON.stringify(item.details, null, 2)}
+                                          
+
+ )} +
+
-
-
- ) - })} + ) + })} +
+ )}
)}
@@ -483,6 +567,36 @@ export function AnalyzePanel() {
)}
+ + {/* Footer */} +
+
+
+ Press ESC to close +
+ +
+
) diff --git a/frontend/src/components/analyze/VisionSection.tsx b/frontend/src/components/analyze/VisionSection.tsx index d89f754..222356f 100644 --- a/frontend/src/components/analyze/VisionSection.tsx +++ b/frontend/src/components/analyze/VisionSection.tsx @@ -3,7 +3,22 @@ import { useCallback, useMemo, useState } from 'react' import clsx from 'clsx' import Link from 'next/link' -import { Copy, Check, Sparkles, Lock, RefreshCw, Mail, Target, Coins, Info } from 'lucide-react' +import { + Copy, + Check, + Sparkles, + Lock, + RefreshCw, + Mail, + Target, + Coins, + Info, + Rocket, + Users, + Radio, + Lightbulb, + ChevronRight, +} from 'lucide-react' import { api } from '@/lib/api' import { useStore } from '@/lib/store' @@ -34,6 +49,55 @@ async function copyToClipboard(text: string) { } } +// Card component for consistent styling +function VisionCard({ + icon: Icon, + iconColor, + title, + children, + copyValue, + className, +}: { + icon: typeof Target + iconColor: string + title: string + children: React.ReactNode + copyValue?: string + className?: string +}) { + const [copied, setCopied] = useState(false) + + const handleCopy = async () => { + if (!copyValue) return + const ok = await copyToClipboard(copyValue) + setCopied(ok) + if (ok) setTimeout(() => setCopied(false), 1200) + } + + return ( +
+
+
+ + {title} +
+ {copyValue && ( + + )} +
+
+ {children} +
+
+ ) +} + export function VisionSection({ domain }: { domain: string }) { const subscription = useStore((s) => s.subscription) const tier = (subscription?.tier || 'scout').toLowerCase() @@ -42,7 +106,6 @@ export function VisionSection({ domain }: { domain: string }) { const [data, setData] = useState(null) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) - const [copiedKey, setCopiedKey] = useState(null) const headline = useMemo(() => domain?.trim().toLowerCase() || '', [domain]) @@ -60,38 +123,32 @@ export function VisionSection({ domain }: { domain: string }) { } }, [headline]) - const copy = useCallback(async (key: string, value: string) => { - const ok = await copyToClipboard(value) - setCopiedKey(ok ? key : null) - if (ok) setTimeout(() => setCopiedKey(null), 1200) - }, []) - + // Upgrade CTA for Scout users if (!canUse) { return ( -
+
-
+
VISION - Trader+ + + Trader+ +
-

- Turn a domain into a buyer-ready business story: pitch, ideal buyer, and outreach angle. +

+ AI-powered business concept, buyer persona, and outreach strategy for this domain.

Upgrade to unlock - + - - Scout users see a preview only. -
@@ -101,194 +158,147 @@ export function VisionSection({ domain }: { domain: string }) { return (
-
-
-
-
- - Vision Engine - | - cached -
-
{headline}
-
- - Generates a VC-style pitch + a concrete buyer persona + a ready-to-send outreach message. -
-
-
- + {/* Controls */} +
+
+ + Generates business insights via AI +
+
+ + {data && ( -
+ )}
+ {/* Error */} {error && ( -
+
{error}
)} + {/* Empty State */} {!data && !loading && !error && ( -
- Click Generate to create a Vision for this domain. +
+ +

+ Click Generate to create AI insights +

)} + {/* Results */} {data && ( -
- {/* Business concept */} -
-
-
- -
Business Concept
-
- +
+ {/* Business Concept */} + +

{data.result.business_concept}

+
+ Vertical: + + {data.result.industry_vertical} +
-
{data.result.business_concept}
-
- Vertical: {data.result.industry_vertical} -
-
+ - {/* Buyer persona */} -
-
-
- -
Ideal Buyer
-
- -
-
{data.result.buyer_persona}
-
+ {/* Ideal Buyer */} + +

{data.result.buyer_persona}

+
- {/* Outreach */} -
-
-
- -
Outreach Draft
+ {/* Outreach Draft */} + +
+
+
Subject
+
{data.result.cold_email_subject}
- -
-
-
Subject
-
{data.result.cold_email_subject}
-
-
-
Body
-
{data.result.cold_email_body}
-
-
- - {/* Monetization + radio test */} -
-
-
-
- -
Monetization
+
+
Body
+
+ {data.result.cold_email_body}
-
-
{data.result.monetization_idea}
+ -
-
-
- -
Radio Test
+ {/* Monetization + Radio Test Row */} +
+ +

{data.result.monetization_idea}

+
+ + +
+
{data.result.radio_test_score}
+
+ 1–10
higher = better
-
-
-
{data.result.radio_test_score}
-
1–10 (higher is better)
-
-
- Measures how easy the name is to remember and spell when heard. -
-
+
{/* Reasoning */} -
-
-
- -
Why this is valuable
-
- + +

{data.result.reasoning}

+
+ {data.cached ? 'Cached' : 'Fresh'} • {new Date(data.generated_at).toLocaleDateString()} + {data.model}
-
{data.result.reasoning}
-
- {data.cached ? 'Cached' : 'Fresh'} • {new Date(data.generated_at).toLocaleString()} • {data.model} -
-
+
)}
) } -