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)) status["domain_count"] = len(_parse_domains_from_zone(content))
return status return status

View File

@ -178,3 +178,4 @@ echo "Then reload: systemctl reload coredns"
echo "" echo ""
echo "Test with: dig @localhost akaya.ch" echo "Test with: dig @localhost akaya.ch"
echo "==========================================" 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({ function ActivateModal({
isOpen, isOpen,
onClose, onClose,
@ -60,58 +62,29 @@ function ActivateModal({
const [loadingDomains, setLoadingDomains] = useState(true) const [loadingDomains, setLoadingDomains] = useState(true)
const [loading, setLoading] = useState(false) const [loading, setLoading] = useState(false)
const [error, setError] = useState<string | null>(null) const [error, setError] = useState<string | null>(null)
const [step, setStep] = useState<1 | 2>(1) const [step, setStep] = useState<1 | 2 | 3>(1)
const [activation, setActivation] = useState<null | { const [activatedDomainId, setActivatedDomainId] = useState<number | null>(null)
domain_id: number const [landing, setLanding] = useState<null | {
domain: string
status: string
dns_instructions: {
domain: string
nameservers: string[]
cname_host: string
cname_target: string
verification_url: string
}
landing?: {
template: string
headline: string headline: string
seo_intro: string seo_intro: string
cta_label: string cta_label: string
model?: string | null
generated_at?: string | null
} | null
}>(null) }>(null)
const [dnsChecking, setDnsChecking] = useState(false) const [dnsChecking, setDnsChecking] = useState(false)
const [dnsResult, setDnsResult] = useState<null | { const [dnsVerified, setDnsVerified] = useState(false)
verified: boolean const [copied, setCopied] = useState(false)
expected_ns: string[]
actual_ns: string[]
cname_ok: boolean
error: string | null
}>(null)
const [copied, setCopied] = useState<string | null>(null)
const [previewLoading, setPreviewLoading] = useState(false) const [previewLoading, setPreviewLoading] = useState(false)
const [previewError, setPreviewError] = useState<string | null>(null) const [previewError, setPreviewError] = useState<string | null>(null)
const [preview, setPreview] = useState<null | { const [preview, setPreview] = useState<null | {
template: string
headline: string headline: string
seo_intro: string seo_intro: string
cta_label: string cta_label: string
niche: string
color_scheme: string
model: string
generated_at: string
cached: boolean
}>(null) }>(null)
useEffect(() => { useEffect(() => {
if (!isOpen) return if (!isOpen) return
if (prefillDomain) { if (prefillDomain) {
setSelectedDomain(prefillDomain) setSelectedDomain(prefillDomain)
setStep(1)
setActivation(null)
setDnsResult(null)
} }
const fetchVerifiedDomains = async () => { const fetchVerifiedDomains = async () => {
setLoadingDomains(true) setLoadingDomains(true)
@ -125,26 +98,28 @@ function ActivateModal({
} }
} }
fetchVerifiedDomains() fetchVerifiedDomains()
}, [isOpen]) }, [isOpen, prefillDomain])
useEffect(() => { useEffect(() => {
if (!isOpen) return if (!isOpen) return
setStep(1) setStep(1)
setActivation(null) setActivatedDomainId(null)
setDnsResult(null) setLanding(null)
setDnsChecking(false) setDnsChecking(false)
setDnsVerified(false)
setError(null) setError(null)
setSelectedDomain('') setSelectedDomain(prefillDomain || '')
setPreview(null) setPreview(null)
setPreviewError(null) setPreviewError(null)
setPreviewLoading(false) setPreviewLoading(false)
}, [isOpen]) setCopied(false)
}, [isOpen, prefillDomain])
const copyToClipboard = async (value: string, key: string) => { const copyIP = async () => {
try { try {
await navigator.clipboard.writeText(value) await navigator.clipboard.writeText(YIELD_SERVER_IP)
setCopied(key) setCopied(true)
setTimeout(() => setCopied(null), 1200) setTimeout(() => setCopied(false), 2000)
} catch { } catch {
// ignore // ignore
} }
@ -156,39 +131,33 @@ function ActivateModal({
setError(null) setError(null)
try { try {
const res = await api.activateYieldDomain(selectedDomain, true) const res = await api.activateYieldDomain(selectedDomain, true)
setActivation({ setActivatedDomainId(res.domain_id)
domain_id: res.domain_id, if (res.landing) {
domain: res.domain, setLanding({
status: res.status, headline: res.landing.headline,
dns_instructions: res.dns_instructions, seo_intro: res.landing.seo_intro,
landing: res.landing || null, cta_label: res.landing.cta_label,
}) })
}
setStep(2) setStep(2)
} catch (err: any) { } catch (err: any) {
setError(err.message || 'Failed') setError(err.message || 'Failed to activate')
} finally { } finally {
setLoading(false) setLoading(false)
} }
} }
const handlePreview = async (refresh: boolean = false) => { const handlePreview = async () => {
if (!selectedDomain) return if (!selectedDomain || !canPreview) return
if (!canPreview) return
setPreviewLoading(true) setPreviewLoading(true)
setPreviewError(null) setPreviewError(null)
setPreview(null) setPreview(null)
try { try {
const res = await api.getYieldLandingPreview(selectedDomain, refresh) const res = await api.getYieldLandingPreview(selectedDomain, false)
setPreview({ setPreview({
template: res.result.template,
headline: res.result.headline, headline: res.result.headline,
seo_intro: res.result.seo_intro, seo_intro: res.result.seo_intro,
cta_label: res.result.cta_label, 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) { } catch (err: any) {
setPreviewError(err.message || 'Preview failed') setPreviewError(err.message || 'Preview failed')
@ -197,105 +166,132 @@ function ActivateModal({
} }
} }
const checkDNS = useCallback(async (domainId: number) => { const checkDNS = async () => {
if (!activatedDomainId) return
setDnsChecking(true) setDnsChecking(true)
setError(null) setError(null)
try { try {
const res = await api.verifyYieldDomainDNS(domainId) const res = await api.verifyYieldDomainDNS(activatedDomainId)
setDnsResult({
verified: res.verified,
expected_ns: res.expected_ns,
actual_ns: res.actual_ns,
cname_ok: res.cname_ok,
error: res.error,
})
if (res.verified) { if (res.verified) {
setDnsVerified(true)
setStep(3)
onSuccess() onSuccess()
} else {
setError('DNS not yet propagated. This can take up to 15 minutes. Try again shortly.')
} }
} catch (err: any) { } catch (err: any) {
setError(err.message || 'DNS check failed') setError(err.message || 'DNS check failed')
} finally { } finally {
setDnsChecking(false) setDnsChecking(false)
} }
}, [onSuccess]) }
if (!isOpen) return null if (!isOpen) return null
return ( return (
<div className="fixed inset-0 z-[110] bg-black/80 flex items-center justify-center p-4" onClick={onClose}> <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="w-full max-w-lg 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]"> {/* Header */}
<div className="flex items-center gap-2"> <div className="flex items-center justify-between p-5 border-b border-white/[0.08]">
<Sparkles className="w-4 h-4 text-accent" /> <div className="flex items-center gap-3">
<span className="text-xs font-mono text-accent uppercase tracking-wider"> <div className="w-10 h-10 bg-accent/10 border border-accent/20 flex items-center justify-center">
{isTycoon ? 'Activate Yield' : 'Yield Preview'} <Sparkles className="w-5 h-5 text-accent" />
</span>
</div> </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> <h2 className="text-base font-bold text-white">
<div className="p-4 space-y-4"> {isTycoon ? 'Activate Yield' : 'Preview Yield Landing'}
{step === 1 && loadingDomains ? ( </h2>
<div className="flex items-center justify-center py-6"> <p className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
<Loader2 className="w-5 h-5 text-accent animate-spin" /> Step {step} of {isTycoon ? 3 : 1}
</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.
</p> </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"> </div>
</div>
<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 Go to Portfolio
<ChevronRight className="w-4 h-4" />
</a> </a>
</div> </div>
) : step === 1 ? ( ) : (
<> <>
<div> <div>
<label className="block text-[9px] font-mono text-white/40 uppercase mb-1.5">Select Domain (DNS Verified)</label> <label className="block text-xs font-medium text-white mb-2">
Which domain do you want to monetize?
</label>
<select <select
value={selectedDomain} value={selectedDomain}
onChange={(e) => setSelectedDomain(e.target.value)} onChange={(e) => { setSelectedDomain(e.target.value); setPreview(null); setPreviewError(null) }}
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" 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"
> >
<option value=""> Select a domain </option> <option value="">Select a domain...</option>
{verifiedDomains.map(d => ( {verifiedDomains.map(d => (
<option key={d.id} value={d.domain}>{d.domain}</option> <option key={d.id} value={d.domain}>{d.domain}</option>
))} ))}
</select> </select>
</div> <p className="text-[10px] text-white/30 mt-2">
<div className={clsx( Only DNS-verified domains from your portfolio are shown.
"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> </p>
)}
</div> </div>
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</div>}
{/* 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 for non-Tycoon */}
{!isTycoon && ( {!isTycoon && (
<> <>
<div className="flex gap-2">
<button <button
onClick={() => handlePreview(false)} onClick={handlePreview}
disabled={previewLoading || !selectedDomain} 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" 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" /> : <Sparkles className="w-4 h-4" />} {previewLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Generate Preview Preview Landing Page
</button> </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>
</div>
{previewError && ( {previewError && (
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs"> <div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs">
@ -304,135 +300,188 @@ function ActivateModal({
)} )}
{preview && ( {preview && (
<div className="p-3 bg-[#050505] border border-white/[0.08] space-y-2"> <div className="p-4 bg-[#050505] border border-white/[0.08] space-y-3">
<div className="flex items-center justify-between"> <div className="text-[9px] font-mono text-accent uppercase tracking-wider">Generated Landing Page</div>
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Landing Page Preview</div> <h3 className="text-base font-bold text-white">{preview.headline}</h3>
<button <p className="text-sm text-white/50 leading-relaxed">{preview.seo_intro}</p>
onClick={() => handlePreview(true)} <div className="pt-3 border-t border-white/10">
className="text-[10px] font-mono text-white/40 hover:text-white" <span className="inline-flex items-center gap-2 px-4 py-2 bg-accent/20 text-accent text-xs font-bold">
disabled={previewLoading} {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"
> >
Refresh <Crown className="w-4 h-4" />
</button> Upgrade to Tycoon to Activate
</div> </Link>
<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>
)}
</> </>
)} )}
{/* Activate for Tycoon */}
{isTycoon && ( {isTycoon && (
<button onClick={handleActivate} disabled={loading || !selectedDomain} <button
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"> 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" />} {loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />}
Activate Yield Generate Landing & Continue
</button> </button>
)} )}
</> </>
) : ( )}
<>
<div className="p-3 bg-white/[0.02] border border-white/[0.08]">
<div className="flex items-start justify-between gap-3">
<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>
</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>
</div> </div>
)} )}
<div className="space-y-2"> {/* STEP 2: DNS Setup Instructions */}
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Option A (Recommended): Nameservers</div> {step === 2 && (
<div className="bg-[#020202] border border-white/[0.08]"> <div className="space-y-5">
{(activation?.dns_instructions.nameservers || []).map((ns, idx) => ( {/* Success Message */}
<div key={ns} className={clsx("flex items-center justify-between px-3 py-2", idx > 0 && "border-t border-white/[0.06]")}> <div className="p-4 bg-accent/5 border border-accent/20">
<span className="text-xs font-mono text-white/80">{ns}</span> <div className="flex gap-3">
<button onClick={() => copyToClipboard(ns, `ns-${idx}`)} className="p-1.5 border border-white/10 text-white/40 hover:text-white"> <CheckCircle2 className="w-5 h-5 text-accent shrink-0 mt-0.5" />
{copied === `ns-${idx}` ? <Check className="w-4 h-4 text-accent" /> : <Copy className="w-4 h-4" />} <div>
</button> <h4 className="text-sm font-bold text-white mb-1">Landing Page Generated!</h4>
</div> <p className="text-xs text-white/50">
))} Now point your domain to our server to start earning.
</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.
</p> </p>
</div> </div>
</div> </div>
</div>
{dnsResult && ( {/* Landing Preview */}
<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")}> {landing && (
<div className="flex items-center justify-between gap-2"> <div className="p-4 bg-[#050505] border border-white/[0.08] space-y-2">
<span>{dnsResult.verified ? 'Connected. Domain is active.' : 'Not connected yet. Waiting for DNS propagation.'}</span> <div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Your Landing Page</div>
{dnsResult.verified ? <CheckCircle2 className="w-4 h-4 text-accent" /> : <Clock className="w-4 h-4 text-amber-400" />} <h3 className="text-sm font-bold text-white">{landing.headline}</h3>
</div> <p className="text-xs text-white/50">{landing.seo_intro}</p>
{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>
)}
</div> </div>
)} )}
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</div>} {/* 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 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>
</div>
{/* 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"> <div className="flex gap-2">
<button <button
onClick={() => { setStep(1); setActivation(null); setDnsResult(null) }} onClick={() => setStep(1)}
className="flex-1 py-2.5 border border-white/10 text-white/60 text-xs font-bold uppercase" className="flex-1 py-3 border border-white/10 text-white/60 text-xs font-bold uppercase hover:bg-white/5 transition-colors"
> >
Back Back
</button> </button>
<button <button
onClick={() => activation?.domain_id && checkDNS(activation.domain_id)} onClick={checkDNS}
disabled={dnsChecking || !activation?.domain_id} disabled={dnsChecking}
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" 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" />} {dnsChecking ? <Loader2 className="w-4 h-4 animate-spin" /> : <RefreshCw className="w-4 h-4" />}
Verify DNS Verify DNS
</button> </button>
</div> </div>
</div>
{dnsResult?.verified && ( </div>
<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>
)} )}
</>
{/* 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>
</div> </div>