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 headline: string
status: string seo_intro: string
dns_instructions: { cta_label: string
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
}>(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,242 +166,322 @@ 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>
<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> </div>
) : step === 1 && verifiedDomains.length === 0 ? ( <div>
<div className="text-center py-6"> <h2 className="text-base font-bold text-white">
<AlertCircle className="w-8 h-8 text-amber-400 mx-auto mb-3" /> {isTycoon ? 'Activate Yield' : 'Preview Yield Landing'}
<h3 className="text-sm font-bold text-white mb-2">No Verified Domains</h3> </h2>
<p className="text-xs text-white/50 mb-4"> <p className="text-[10px] font-mono text-white/40 uppercase tracking-wider">
You need to add domains to your portfolio and verify DNS ownership before activating Yield. Step {step} of {isTycoon ? 3 : 1}
</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">
Go to Portfolio
</a>
</div> </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> </div>
{error && <div className="p-2 bg-rose-500/10 border border-rose-500/20 text-rose-400 text-xs">{error}</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" />
{!isTycoon && ( </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"> <div>
<button <label className="block text-xs font-medium text-white mb-2">
onClick={() => handlePreview(false)} Which domain do you want to monetize?
disabled={previewLoading || !selectedDomain} </label>
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" <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" />} <option value="">Select a domain...</option>
Generate Preview {verifiedDomains.map(d => (
</button> <option key={d.id} value={d.domain}>{d.domain}</option>
<Link ))}
href="/pricing" </select>
className="px-3 py-2.5 bg-accent text-black text-xs font-bold uppercase flex items-center justify-center" <p className="text-[10px] text-white/30 mt-2">
> Only DNS-verified domains from your portfolio are shown.
Upgrade </p>
</Link>
</div> </div>
{previewError && ( {/* Info Box */}
<div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs"> <div className={clsx(
{previewError} "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> </div>
)} )}
{preview && ( {/* Preview for non-Tycoon */}
<div className="p-3 bg-[#050505] border border-white/[0.08] space-y-2"> {!isTycoon && (
<div className="flex items-center justify-between"> <>
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Landing Page Preview</div> <button
<button onClick={handlePreview}
onClick={() => handlePreview(true)} disabled={previewLoading || !selectedDomain}
className="text-[10px] font-mono text-white/40 hover:text-white" 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"
disabled={previewLoading} >
> {previewLoading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Eye className="w-4 h-4" />}
Refresh Preview Landing Page
</button> </button>
</div>
<div className="text-sm font-bold text-white">{preview.headline}</div> {previewError && (
<div className="text-xs text-white/50">{preview.seo_intro}</div> <div className="p-3 bg-rose-500/10 border border-rose-500/20 text-rose-300 text-xs">
<div className="flex items-center justify-between text-[10px] font-mono text-white/40 pt-2 border-t border-white/10"> {previewError}
<span>CTA: <span className="text-white/70">{preview.cta_label}</span></span> </div>
<span>{preview.cached ? 'Cached' : 'Fresh'} {preview.model}</span> )}
</div>
</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 && ( {/* STEP 2: DNS Setup Instructions */}
<button onClick={handleActivate} disabled={loading || !selectedDomain} {step === 2 && (
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"> <div className="space-y-5">
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : <Sparkles className="w-4 h-4" />} {/* Success Message */}
Activate Yield <div className="p-4 bg-accent/5 border border-accent/20">
</button> <div className="flex gap-3">
)} <CheckCircle2 className="w-5 h-5 text-accent shrink-0 mt-0.5" />
</>
) : (
<>
<div className="p-3 bg-white/[0.02] border border-white/[0.08]">
<div className="flex items-start justify-between gap-3">
<div> <div>
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Domain</div> <h4 className="text-sm font-bold text-white mb-1">Landing Page Generated!</h4>
<div className="text-sm font-bold text-white font-mono">{activation?.domain}</div> <p className="text-xs text-white/50">
Now point your domain to our server to start earning.
</p>
</div> </div>
<StatusBadge status={activation?.status || 'pending'} />
</div> </div>
</div> </div>
{activation?.landing?.headline && ( {/* Landing Preview */}
<div className="p-3 bg-[#050505] border border-white/[0.08] space-y-2"> {landing && (
<div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Landing Page (Generated)</div> <div className="p-4 bg-[#050505] border border-white/[0.08] space-y-2">
<div className="text-sm font-bold text-white">{activation.landing.headline}</div> <div className="text-[9px] font-mono text-white/40 uppercase tracking-wider">Your Landing Page</div>
<div className="text-xs text-white/50">{activation.landing.seo_intro}</div> <h3 className="text-sm font-bold text-white">{landing.headline}</h3>
<div className="text-[10px] font-mono text-white/40"> <p className="text-xs text-white/50">{landing.seo_intro}</p>
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"> {/* DNS Instructions */}
<div className="text-[10px] font-mono text-white/40 uppercase tracking-wider">Option A (Recommended): Nameservers</div> <div className="space-y-3">
<div className="bg-[#020202] border border-white/[0.08]"> <h3 className="text-sm font-bold text-white flex items-center gap-2">
{(activation?.dns_instructions.nameservers || []).map((ns, idx) => ( <span className="w-6 h-6 bg-accent text-black text-xs font-bold flex items-center justify-center">1</span>
<div key={ns} className={clsx("flex items-center justify-between px-3 py-2", idx > 0 && "border-t border-white/[0.06]")}> Set up DNS at your registrar
<span className="text-xs font-mono text-white/80">{ns}</span> </h3>
<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" />} <div className="p-4 bg-[#020202] border border-white/[0.08] space-y-4">
</button> <p className="text-sm text-white/70">
</div> Go to your domain registrar (where you bought <span className="text-white font-bold">{selectedDomain}</span>) and add this DNS record:
))}
</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 className="grid grid-cols-3 gap-2 text-center">
<div className="p-3 bg-white/5 border border-white/10">
{dnsResult && ( <div className="text-[9px] font-mono text-white/40 uppercase mb-1">Type</div>
<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="text-lg font-bold text-white font-mono">A</div>
<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> </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>
)}
{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> </div>
{dnsResult?.verified && ( {/* Verify Button */}
<button <div className="space-y-3">
onClick={() => { onClose(); onSuccess() }} <h3 className="text-sm font-bold text-white flex items-center gap-2">
className="w-full py-2.5 bg-white text-black text-xs font-bold uppercase flex items-center justify-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
View Yield Dashboard <ChevronRight className="w-4 h-4" /> </h3>
</button>
)} <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>
</div> </div>