feat: Redesign Yield activation wizard with clear 3-step flow
Some checks failed
CI / Frontend Lint & Type Check (push) Has been cancelled
CI / Frontend Build (push) Has been cancelled
CI / Backend Lint (push) Has been cancelled
CI / Backend Tests (push) Has been cancelled
CI / Docker Build (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
Deploy / Build & Push Images (push) Has been cancelled
Deploy / Deploy to Server (push) Has been cancelled
Deploy / Notify (push) Has been cancelled

- Step 1: Select domain from verified portfolio
- Step 2: Simple DNS instructions with A-record to 46.235.147.194
- Step 3: Success confirmation

Much cleaner UI with:
- Visual step indicators
- Copy IP button
- Clear explanations
- Preview for non-Tycoon users
This commit is contained in:
2025-12-18 15:13:06 +01:00
parent 800379b581
commit dae4da3f38
3 changed files with 308 additions and 257 deletions

View File

@ -253,3 +253,4 @@ def check_coredns_status() -> dict:
status["domain_count"] = len(_parse_domains_from_zone(content))
return status

View File

@ -178,3 +178,4 @@ echo "Then reload: systemctl reload coredns"
echo ""
echo "Test with: dig @localhost akaya.ch"
echo "=========================================="

View File

@ -36,9 +36,11 @@ function StatusBadge({ status }: { status: string }) {
}
// ============================================================================
// ACTIVATE MODAL - Only verified portfolio domains
// ACTIVATE MODAL - Simple 3-step wizard
// ============================================================================
const YIELD_SERVER_IP = '46.235.147.194'
function ActivateModal({
isOpen,
onClose,
@ -60,58 +62,29 @@ function ActivateModal({
const [loadingDomains, setLoadingDomains] = useState(true)
const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null)
const [step, setStep] = useState<1 | 2>(1)
const [activation, setActivation] = useState<null | {
domain_id: number
domain: string
status: string
dns_instructions: {
domain: string
nameservers: string[]
cname_host: string
cname_target: string
verification_url: string
}
landing?: {
template: string
headline: string
seo_intro: string
cta_label: string
model?: string | null
generated_at?: string | null
} | null
const [step, setStep] = useState<1 | 2 | 3>(1)
const [activatedDomainId, setActivatedDomainId] = useState<number | null>(null)
const [landing, setLanding] = useState<null | {
headline: string
seo_intro: string
cta_label: string
}>(null)
const [dnsChecking, setDnsChecking] = useState(false)
const [dnsResult, setDnsResult] = useState<null | {
verified: boolean
expected_ns: string[]
actual_ns: string[]
cname_ok: boolean
error: string | null
}>(null)
const [copied, setCopied] = useState<string | null>(null)
const [dnsVerified, setDnsVerified] = useState(false)
const [copied, setCopied] = useState(false)
const [previewLoading, setPreviewLoading] = useState(false)
const [previewError, setPreviewError] = useState<string | null>(null)
const [preview, setPreview] = useState<null | {
template: string
headline: string
seo_intro: string
cta_label: string
niche: string
color_scheme: string
model: string
generated_at: string
cached: boolean
}>(null)
useEffect(() => {
if (!isOpen) return
if (prefillDomain) {
setSelectedDomain(prefillDomain)
setStep(1)
setActivation(null)
setDnsResult(null)
}
const fetchVerifiedDomains = async () => {
setLoadingDomains(true)
@ -125,26 +98,28 @@ function ActivateModal({
}
}
fetchVerifiedDomains()
}, [isOpen])
}, [isOpen, prefillDomain])
useEffect(() => {
if (!isOpen) return
setStep(1)
setActivation(null)
setDnsResult(null)
setActivatedDomainId(null)
setLanding(null)
setDnsChecking(false)
setDnsVerified(false)
setError(null)
setSelectedDomain('')
setSelectedDomain(prefillDomain || '')
setPreview(null)
setPreviewError(null)
setPreviewLoading(false)
}, [isOpen])
setCopied(false)
}, [isOpen, prefillDomain])
const copyToClipboard = async (value: string, key: string) => {
const copyIP = async () => {
try {
await navigator.clipboard.writeText(value)
setCopied(key)
setTimeout(() => setCopied(null), 1200)
await navigator.clipboard.writeText(YIELD_SERVER_IP)
setCopied(true)
setTimeout(() => setCopied(false), 2000)
} catch {
// ignore
}
@ -156,39 +131,33 @@ function ActivateModal({
setError(null)
try {
const res = await api.activateYieldDomain(selectedDomain, true)
setActivation({
domain_id: res.domain_id,
domain: res.domain,
status: res.status,
dns_instructions: res.dns_instructions,
landing: res.landing || null,
})
setActivatedDomainId(res.domain_id)
if (res.landing) {
setLanding({
headline: res.landing.headline,
seo_intro: res.landing.seo_intro,
cta_label: res.landing.cta_label,
})
}
setStep(2)
} catch (err: any) {
setError(err.message || 'Failed')
setError(err.message || 'Failed to activate')
} finally {
setLoading(false)
}
}
const handlePreview = async (refresh: boolean = false) => {
if (!selectedDomain) return
if (!canPreview) return
const handlePreview = async () => {
if (!selectedDomain || !canPreview) return
setPreviewLoading(true)
setPreviewError(null)
setPreview(null)
try {
const res = await api.getYieldLandingPreview(selectedDomain, refresh)
const res = await api.getYieldLandingPreview(selectedDomain, false)
setPreview({
template: res.result.template,
headline: res.result.headline,
seo_intro: res.result.seo_intro,
cta_label: res.result.cta_label,
niche: res.result.niche,
color_scheme: res.result.color_scheme,
model: res.model,
generated_at: res.generated_at,
cached: res.cached,
})
} catch (err: any) {
setPreviewError(err.message || 'Preview failed')
@ -197,242 +166,322 @@ function ActivateModal({
}
}
const checkDNS = useCallback(async (domainId: number) => {
const checkDNS = async () => {
if (!activatedDomainId) return
setDnsChecking(true)
setError(null)
try {
const res = await api.verifyYieldDomainDNS(domainId)
setDnsResult({
verified: res.verified,
expected_ns: res.expected_ns,
actual_ns: res.actual_ns,
cname_ok: res.cname_ok,
error: res.error,
})
const res = await api.verifyYieldDomainDNS(activatedDomainId)
if (res.verified) {
setDnsVerified(true)
setStep(3)
onSuccess()
} else {
setError('DNS not yet propagated. This can take up to 15 minutes. Try again shortly.')
}
} catch (err: any) {
setError(err.message || 'DNS check failed')
} finally {
setDnsChecking(false)
}
}, [onSuccess])
}
if (!isOpen) return null
return (
<div className="fixed inset-0 z-[110] bg-black/80 flex items-center justify-center p-4" onClick={onClose}>
<div className="w-full max-w-md bg-[#0A0A0A] border border-white/[0.08]" onClick={(e) => e.stopPropagation()}>
<div className="flex items-center justify-between p-4 border-b border-white/[0.08]">
<div className="flex items-center gap-2">
<Sparkles className="w-4 h-4 text-accent" />
<span className="text-xs font-mono text-accent uppercase tracking-wider">
{isTycoon ? 'Activate Yield' : 'Yield Preview'}
</span>
</div>
<button onClick={onClose} className="w-8 h-8 flex items-center justify-center border border-white/10 text-white/40"><X className="w-4 h-4" /></button>
</div>
<div className="p-4 space-y-4">
{step === 1 && loadingDomains ? (
<div className="flex items-center justify-center py-6">
<Loader2 className="w-5 h-5 text-accent animate-spin" />
<div className="w-full max-w-lg bg-[#0A0A0A] border border-white/[0.08]" onClick={(e) => e.stopPropagation()}>
{/* Header */}
<div className="flex items-center justify-between p-5 border-b border-white/[0.08]">
<div className="flex items-center gap-3">
<div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
<Sparkles className="w-5 h-5 text-accent" />
</div>
) : step === 1 && verifiedDomains.length === 0 ? (
<div className="text-center py-6">
<AlertCircle className="w-8 h-8 text-amber-400 mx-auto mb-3" />
<h3 className="text-sm font-bold text-white mb-2">No Verified Domains</h3>
<p className="text-xs text-white/50 mb-4">
You need to add domains to your portfolio and verify DNS ownership before activating Yield.
<div>
<h2 className="text-base font-bold text-white">
{isTycoon ? 'Activate Yield' : 'Preview Yield Landing'}
</h2>
<p className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
Step {step} of {isTycoon ? 3 : 1}
</p>
<a href="/terminal/portfolio" className="inline-flex items-center gap-2 px-4 py-2 bg-accent text-black text-xs font-bold uppercase">
Go to Portfolio
</a>
</div>
) : step === 1 ? (
<>
<div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Select Domain (DNS Verified)</label>
<select
value={selectedDomain}
onChange={(e) => setSelectedDomain(e.target.value)}
className="w-full px-3 py-2.5 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50"
>
<option value=""> Select a domain </option>
{verifiedDomains.map(d => (
<option key={d.id} value={d.domain}>{d.domain}</option>
))}
</select>
</div>
<div className={clsx(
"p-3 border text-xs font-mono",
isTycoon ? "bg-accent/5 border-accent/20 text-accent/80" : "bg-white/[0.02] border-white/[0.08] text-white/50"
)}>
{isTycoon ? (
<p>Only DNS-verified domains from your portfolio can be activated for Yield.</p>
) : (
<p>
Yield is <span className="text-white/80 font-bold">Tycoon-only</span>. On Trader you can preview the landing page that will be generated.
</p>
)}
</div>
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</div>}
{!isTycoon && (
<button onClick={onClose} className="w-9 h-9 flex items-center justify-center border border-white/10 text-white/40 hover:text-white hover:border-white/20 transition-colors">
<X className="w-4 h-4" />
</button>
</div>
{/* Content */}
<div className="p-5">
{/* STEP 1: Select Domain */}
{step === 1 && (
<div className="space-y-5">
{loadingDomains ? (
<div className="flex items-center justify-center py-10">
<Loader2 className="w-6 h-6 text-accent animate-spin" />
</div>
) : verifiedDomains.length === 0 ? (
<div className="text-center py-8">
<AlertCircle className="w-12 h-12 text-amber-400/50 mx-auto mb-4" />
<h3 className="text-lg font-bold text-white mb-2">No Verified Domains</h3>
<p className="text-sm text-white/50 mb-6 max-w-xs mx-auto">
First, add domains to your portfolio and verify DNS ownership.
</p>
<a href="/terminal/portfolio" className="inline-flex items-center gap-2 px-5 py-3 bg-accent text-black text-xs font-bold uppercase tracking-wider">
Go to Portfolio
<ChevronRight className="w-4 h-4" />
</a>
</div>
) : (
<>
<div className="flex gap-2">
<button
onClick={() => handlePreview(false)}
disabled={previewLoading || !selectedDomain}
className="flex-1 py-2.5 bg-white/10 text-white text-xs font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50"
<div>
<label className="block text-xs font-medium text-white mb-2">
Which domain do you want to monetize?
</label>
<select
value={selectedDomain}
onChange={(e) => { setSelectedDomain(e.target.value); setPreview(null); setPreviewError(null) }}
className="w-full px-4 py-3 bg-white/5 border border-white/10 text-white text-sm font-mono outline-none focus:border-accent/50 transition-colors"
>
{previewLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
Generate Preview
</button>
<Link
href="/pricing"
className="px-3 py-2.5 bg-accent text-black text-xs font-bold uppercase flex items-center justify-center"
>
Upgrade
</Link>
<option value="">Select a domain...</option>
{verifiedDomains.map(d => (
<option key={d.id} value={d.domain}>{d.domain}</option>
))}
</select>
<p className="text-[10px] text-white/30 mt-2">
Only DNS-verified domains from your portfolio are shown.
</p>
</div>
{previewError && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs">
{previewError}
{/* Info Box */}
<div className={clsx(
"p-4 border",
isTycoon ? "bg-accent/5 border-accent/20" : "bg-white/[0.02] border-white/[0.08]"
)}>
<div className="flex gap-3">
<div className="shrink-0 w-8 h-8 bg-white/5 flex items-center justify-center">
<Zap className={clsx("w-4 h-4", isTycoon ? "text-accent" : "text-white/40")} />
</div>
<div>
<h4 className="text-sm font-bold text-white mb-1">
{isTycoon ? 'How Yield Works' : 'Yield is Tycoon-Only'}
</h4>
<p className="text-xs text-white/50 leading-relaxed">
{isTycoon
? 'We generate an AI-powered landing page for your domain. When visitors click, you earn revenue through our affiliate network.'
: 'You can preview the AI-generated landing page, but activation requires a Tycoon subscription.'}
</p>
</div>
</div>
</div>
{error && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs font-mono">
{error}
</div>
)}
{preview && (
<div className="p-3 bg-[#050505] border border-white/[0.08] space-y-2">
<div className="flex items-center justify-between">
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Landing Page Preview</div>
<button
onClick={() => handlePreview(true)}
className="text-[10px] font-mono text-white/40 hover:text-white"
disabled={previewLoading}
>
Refresh
</button>
</div>
<div className="text-sm font-bold text-white">{preview.headline}</div>
<div className="text-xs text-white/50">{preview.seo_intro}</div>
<div className="flex items-center justify-between text-[10px] font-mono text-white/40 pt-2 border-t border-white/10">
<span>CTA: <span className="text-white/70">{preview.cta_label}</span></span>
<span>{preview.cached ? 'Cached' : 'Fresh'} {preview.model}</span>
</div>
</div>
{/* Preview for non-Tycoon */}
{!isTycoon && (
<>
<button
onClick={handlePreview}
disabled={previewLoading || !selectedDomain}
className="w-full py-3 bg-white/10 text-white text-sm font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50 hover:bg-white/15 transition-colors"
>
{previewLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Preview Landing Page
</button>
{previewError && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs">
{previewError}
</div>
)}
{preview && (
<div className="p-4 bg-[#050505] border border-white/[0.08] space-y-3">
<div className="text-[9px] font-mono text-accent uppercase tracking-wider">Generated Landing Page</div>
<h3 className="text-base font-bold text-white">{preview.headline}</h3>
<p className="text-sm text-white/50 leading-relaxed">{preview.seo_intro}</p>
<div className="pt-3 border-t border-white/10">
<span className="inline-flex items-center gap-2 px-4 py-2 bg-accent/20 text-accent text-xs font-bold">
{preview.cta_label}
</span>
</div>
</div>
)}
<Link
href="/pricing"
className="w-full py-3 bg-accent text-black text-sm font-bold uppercase flex items-center justify-center gap-2 hover:bg-white transition-colors"
>
<Crown className="w-4 h-4" />
Upgrade to Tycoon to Activate
</Link>
</>
)}
{/* Activate for Tycoon */}
{isTycoon && (
<button
onClick={handleActivate}
disabled={loading || !selectedDomain}
className="w-full py-3.5 bg-accent text-black text-sm font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50 hover:bg-white transition-colors"
>
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
Generate Landing & Continue
</button>
)}
</>
)}
</div>
)}
{isTycoon && (
<button onClick={handleActivate} disabled={loading || !selectedDomain}
className="w-full py-2.5 bg-accent text-black text-xs font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50">
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
Activate Yield
</button>
)}
</>
) : (
<>
<div className="p-3 bg-white/[0.02] border border-white/[0.08]">
<div className="flex items-start justify-between gap-3">
{/* STEP 2: DNS Setup Instructions */}
{step === 2 && (
<div className="space-y-5">
{/* Success Message */}
<div className="p-4 bg-accent/5 border border-accent/20">
<div className="flex gap-3">
<CheckCircle2 className="w-5 h-5 text-accent shrink-0 mt-0.5" />
<div>
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Domain</div>
<div className="text-sm font-bold text-white font-mono">{activation?.domain}</div>
<h4 className="text-sm font-bold text-white mb-1">Landing Page Generated!</h4>
<p className="text-xs text-white/50">
Now point your domain to our server to start earning.
</p>
</div>
<StatusBadge status={activation?.status || 'pending'} />
</div>
</div>
{activation?.landing?.headline && (
<div className="p-3 bg-[#050505] border border-white/[0.08] space-y-2">
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Landing Page (Generated)</div>
<div className="text-sm font-bold text-white">{activation.landing.headline}</div>
<div className="text-xs text-white/50">{activation.landing.seo_intro}</div>
<div className="text-[10px] font-mono text-white/40">
CTA: <span className="text-white/70">{activation.landing.cta_label}</span>
{activation.landing.model ? <span className="text-white/20"> {activation.landing.model}</span> : null}
</div>
{/* Landing Preview */}
{landing && (
<div className="p-4 bg-[#050505] border border-white/[0.08] space-y-2">
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Your Landing Page</div>
<h3 className="text-sm font-bold text-white">{landing.headline}</h3>
<p className="text-xs text-white/50">{landing.seo_intro}</p>
</div>
)}
<div className="space-y-2">
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Option A (Recommended): Nameservers</div>
<div className="bg-[#020202] border border-white/[0.08]">
{(activation?.dns_instructions.nameservers || []).map((ns, idx) => (
<div key={ns} className={clsx("flex items-center justify-between px-3 py-2", idx > 0 && "border-t border-white/[0.06]")}>
<span className="text-xs font-mono text-white/80">{ns}</span>
<button onClick={() => copyToClipboard(ns, `ns-${idx}`)} className="p-1.5 border border-white/10 text-white/40 hover:text-white">
{copied === `ns-${idx}` ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />}
</button>
</div>
))}
</div>
</div>
<div className="space-y-2">
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Option B: CNAME / ALIAS</div>
<div className="bg-[#020202] border border-white/[0.08] p-3 space-y-2">
<div className="flex items-center justify-between">
<div className="text-xs font-mono text-white/70">
<span className="text-white/40">Host:</span> {activation?.dns_instructions.cname_host} <span className="text-white/40"> Target:</span> {activation?.dns_instructions.cname_target}
</div>
<button onClick={() => copyToClipboard(activation?.dns_instructions.cname_target || '', `cname-target`)} className="p-1.5 border border-white/10 text-white/40 hover:text-white">
{copied === `cname-target` ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />}
</button>
</div>
<p className="text-[10px] font-mono text-white/35">
Some DNS providers use ALIAS/ANAME for apex. We accept both CNAME and ALIAS-style flattening.
{/* DNS Instructions */}
<div className="space-y-3">
<h3 className="text-sm font-bold text-white flex items-center gap-2">
<span className="w-6 h-6 bg-accent text-black text-xs font-bold flex items-center justify-center">1</span>
Set up DNS at your registrar
</h3>
<div className="p-4 bg-[#020202] border border-white/[0.08] space-y-4">
<p className="text-sm text-white/70">
Go to your domain registrar (where you bought <span className="text-white font-bold">{selectedDomain}</span>) and add this DNS record:
</p>
</div>
</div>
{dnsResult && (
<div className={clsx("p-3 border text-xs font-mono", dnsResult.verified ? "bg-accent/5 border-accent/20 text-accent/80" : "bg-amber-400/5 border-amber-400/20 text-amber-400/80")}>
<div className="flex items-center justify-between gap-2">
<span>{dnsResult.verified ? 'Connected. Domain is active.' : 'Not connected yet. Waiting for DNS propagation.'}</span>
{dnsResult.verified ? <CheckCircle2 className="w-4 h-4 text-accent" /> : <Clock className="w-4 h-4 text-amber-400" />}
</div>
{dnsResult.error && <div className="mt-2 text-rose-400/80">Error: {dnsResult.error}</div>}
{!dnsResult.verified && (
<div className="mt-2 text-white/40">
<div>Expected NS: {dnsResult.expected_ns?.join(', ') || '—'}</div>
<div>Actual NS: {dnsResult.actual_ns?.join(', ') || '—'}</div>
<div className="grid grid-cols-3 gap-2 text-center">
<div className="p-3 bg-white/5 border border-white/10">
<div className="text-[9px] font-mono text-white/40 uppercase mb-1">Type</div>
<div className="text-lg font-bold text-white font-mono">A</div>
</div>
)}
<div className="p-3 bg-white/5 border border-white/10">
<div className="text-[9px] font-mono text-white/40 uppercase mb-1">Name</div>
<div className="text-lg font-bold text-white font-mono">@</div>
</div>
<div className="p-3 bg-accent/10 border border-accent/20">
<div className="text-[9px] font-mono text-accent/60 uppercase mb-1">Value</div>
<div className="text-sm font-bold text-accent font-mono">{YIELD_SERVER_IP}</div>
</div>
</div>
<button
onClick={copyIP}
className="w-full py-2.5 border border-white/10 text-white/70 text-xs font-mono flex items-center justify-center gap-2 hover:bg-white/5 transition-colors"
>
{copied ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />}
{copied ? 'Copied!' : `Copy IP: ${YIELD_SERVER_IP}`}
</button>
<div className="text-[10px] text-white/30 leading-relaxed">
<strong className="text-white/50">Tip:</strong> The "@" symbol means the root domain. Some registrars use "empty" or the domain name itself instead.
</div>
</div>
)}
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</div>}
<div className="flex gap-2">
<button
onClick={() => { setStep(1); setActivation(null); setDnsResult(null) }}
className="flex-1 py-2.5 border border-white/10 text-white/60 text-xs font-bold uppercase"
>
Back
</button>
<button
onClick={() => activation?.domain_id && checkDNS(activation.domain_id)}
disabled={dnsChecking || !activation?.domain_id}
className="flex-[1.4] py-2.5 bg-accent text-black text-xs font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50"
>
{dnsChecking ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
Verify DNS
</button>
</div>
{dnsResult?.verified && (
<button
onClick={() => { onClose(); onSuccess() }}
className="w-full py-2.5 bg-white text-black text-xs font-bold uppercase flex items-center justify-center gap-2"
>
View Yield Dashboard <ChevronRight className="w-4 h-4" />
</button>
)}
</>
{/* Verify Button */}
<div className="space-y-3">
<h3 className="text-sm font-bold text-white flex items-center gap-2">
<span className="w-6 h-6 bg-white/10 text-white/60 text-xs font-bold flex items-center justify-center">2</span>
Verify connection
</h3>
<p className="text-xs text-white/50">
After saving your DNS settings, click verify. DNS changes can take 5-15 minutes to propagate.
</p>
{error && (
<div className="p-3 bg-amber-500/10 border border-amber-500/20 text-amber-400 text-xs flex items-start gap-2">
<Clock className="w-4 h-4 shrink-0 mt-0.5" />
{error}
</div>
)}
<div className="flex gap-2">
<button
onClick={() => setStep(1)}
className="flex-1 py-3 border border-white/10 text-white/60 text-xs font-bold uppercase hover:bg-white/5 transition-colors"
>
Back
</button>
<button
onClick={checkDNS}
disabled={dnsChecking}
className="flex-[2] py-3 bg-accent text-black text-sm font-bold uppercase flex items-center justify-center gap-2 disabled:opacity-50 hover:bg-white transition-colors"
>
{dnsChecking ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
Verify DNS
</button>
</div>
</div>
</div>
)}
{/* STEP 3: Success */}
{step === 3 && (
<div className="text-center py-6 space-y-5">
<div className="w-16 h-16 bg-accent/10 border border-accent/20 flex items-center justify-center mx-auto">
<CheckCircle2 className="w-8 h-8 text-accent" />
</div>
<div>
<h3 className="text-xl font-bold text-white mb-2">Yield Activated!</h3>
<p className="text-sm text-white/50">
<span className="text-white font-bold">{selectedDomain}</span> is now earning passive income.
</p>
</div>
<div className="p-4 bg-[#050505] border border-white/[0.08] text-left space-y-2">
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">What happens now?</div>
<ul className="text-xs text-white/60 space-y-1.5">
<li className="flex items-start gap-2">
<span className="text-accent"></span>
Visitors to {selectedDomain} see your AI-generated landing page
</li>
<li className="flex items-start gap-2">
<span className="text-accent"></span>
When they click the CTA, you earn revenue
</li>
<li className="flex items-start gap-2">
<span className="text-accent"></span>
Track clicks and earnings in your Yield dashboard
</li>
</ul>
</div>
<button
onClick={onClose}
className="w-full py-3.5 bg-accent text-black text-sm font-bold uppercase flex items-center justify-center gap-2 hover:bg-white transition-colors"
>
View Dashboard
<ChevronRight className="w-4 h-4" />
</button>
</div>
)}
</div>
</div>