From dae4da3f389d04a13a3e0d8fcc569d1d6e4c4483 Mon Sep 17 00:00:00 2001 From: Yves Gugger Date: Thu, 18 Dec 2025 15:13:06 +0100 Subject: [PATCH] feat: Redesign Yield activation wizard with clear 3-step flow - 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 --- backend/app/services/dns_zone_manager.py | 1 + backend/scripts/setup_dns_server.sh | 1 + frontend/src/app/terminal/yield/page.tsx | 563 ++++++++++++----------- 3 files changed, 308 insertions(+), 257 deletions(-) diff --git a/backend/app/services/dns_zone_manager.py b/backend/app/services/dns_zone_manager.py index 86f9d6e..b1ad178 100644 --- a/backend/app/services/dns_zone_manager.py +++ b/backend/app/services/dns_zone_manager.py @@ -253,3 +253,4 @@ def check_coredns_status() -> dict: status["domain_count"] = len(_parse_domains_from_zone(content)) return status + diff --git a/backend/scripts/setup_dns_server.sh b/backend/scripts/setup_dns_server.sh index 4ce0b22..e73a087 100755 --- a/backend/scripts/setup_dns_server.sh +++ b/backend/scripts/setup_dns_server.sh @@ -178,3 +178,4 @@ echo "Then reload: systemctl reload coredns" echo "" echo "Test with: dig @localhost akaya.ch" echo "==========================================" + diff --git a/frontend/src/app/terminal/yield/page.tsx b/frontend/src/app/terminal/yield/page.tsx index 519f8a9..1757f00 100644 --- a/frontend/src/app/terminal/yield/page.tsx +++ b/frontend/src/app/terminal/yield/page.tsx @@ -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(null) - const [step, setStep] = useState<1 | 2>(1) - const [activation, setActivation] = useState(1) + const [activatedDomainId, setActivatedDomainId] = useState(null) + const [landing, setLanding] = useState(null) const [dnsChecking, setDnsChecking] = useState(false) - const [dnsResult, setDnsResult] = useState(null) - const [copied, setCopied] = useState(null) + const [dnsVerified, setDnsVerified] = useState(false) + const [copied, setCopied] = useState(false) const [previewLoading, setPreviewLoading] = useState(false) const [previewError, setPreviewError] = useState(null) const [preview, setPreview] = useState(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 (
-
e.stopPropagation()}> -
-
- - - {isTycoon ? 'Activate Yield' : 'Yield Preview'} - -
- -
-
- {step === 1 && loadingDomains ? ( -
- +
e.stopPropagation()}> + {/* Header */} +
+
+
+
- ) : step === 1 && verifiedDomains.length === 0 ? ( -
- -

No Verified Domains

-

- You need to add domains to your portfolio and verify DNS ownership before activating Yield. +

+

+ {isTycoon ? 'Activate Yield' : 'Preview Yield Landing'} +

+

+ Step {step} of {isTycoon ? 3 : 1}

- - Go to Portfolio -
- ) : step === 1 ? ( - <> -
- - -
-
- {isTycoon ? ( -

Only DNS-verified domains from your portfolio can be activated for Yield.

- ) : ( -

- Yield is Tycoon-only. On Trader you can preview the landing page that will be generated. -

- )}
- {error &&
{error}
} - - {!isTycoon && ( + +
+ + {/* Content */} +
+ {/* STEP 1: Select Domain */} + {step === 1 && ( +
+ {loadingDomains ? ( +
+ +
+ ) : verifiedDomains.length === 0 ? ( +
+ +

No Verified Domains

+

+ First, add domains to your portfolio and verify DNS ownership. +

+ + Go to Portfolio + + +
+ ) : ( <> -
- -
-
{preview.headline}
-
{preview.seo_intro}
-
- CTA: {preview.cta_label} - {preview.cached ? 'Cached' : 'Fresh'} • {preview.model} -
-
+ {/* Preview for non-Tycoon */} + {!isTycoon && ( + <> + + + {previewError && ( +
+ {previewError} +
+ )} + + {preview && ( +
+
Generated Landing Page
+

{preview.headline}

+

{preview.seo_intro}

+
+ + {preview.cta_label} + +
+
+ )} + + + + Upgrade to Tycoon to Activate + + + )} + + {/* Activate for Tycoon */} + {isTycoon && ( + )} )} +
+ )} - {isTycoon && ( - - )} - - ) : ( - <> -
-
+ {/* STEP 2: DNS Setup Instructions */} + {step === 2 && ( +
+ {/* Success Message */} +
+
+
-
Domain
-
{activation?.domain}
+

Landing Page Generated!

+

+ Now point your domain to our server to start earning. +

-
- {activation?.landing?.headline && ( -
-
Landing Page (Generated)
-
{activation.landing.headline}
-
{activation.landing.seo_intro}
-
- CTA: {activation.landing.cta_label} - {activation.landing.model ? • {activation.landing.model} : null} -
+ {/* Landing Preview */} + {landing && ( +
+
Your Landing Page
+

{landing.headline}

+

{landing.seo_intro}

)} -
-
Option A (Recommended): Nameservers
-
- {(activation?.dns_instructions.nameservers || []).map((ns, idx) => ( -
0 && "border-t border-white/[0.06]")}> - {ns} - -
- ))} -
-
- -
-
Option B: CNAME / ALIAS
-
-
-
- Host: {activation?.dns_instructions.cname_host} → Target: {activation?.dns_instructions.cname_target} -
- -
-

- Some DNS providers use ALIAS/ANAME for apex. We accept both CNAME and ALIAS-style flattening. + {/* DNS Instructions */} +

+

+ 1 + Set up DNS at your registrar +

+ +
+

+ Go to your domain registrar (where you bought {selectedDomain}) and add this DNS record:

-
-
- - {dnsResult && ( -
-
- {dnsResult.verified ? 'Connected. Domain is active.' : 'Not connected yet. Waiting for DNS propagation.'} - {dnsResult.verified ? : } -
- {dnsResult.error &&
Error: {dnsResult.error}
} - {!dnsResult.verified && ( -
-
Expected NS: {dnsResult.expected_ns?.join(', ') || '—'}
-
Actual NS: {dnsResult.actual_ns?.join(', ') || '—'}
+ +
+
+
Type
+
A
- )} +
+
Name
+
@
+
+
+
Value
+
{YIELD_SERVER_IP}
+
+
+ + + +
+ Tip: The "@" symbol means the root domain. Some registrars use "empty" or the domain name itself instead. +
- )} - - {error &&
{error}
} - -
- -
- {dnsResult?.verified && ( - - )} - + {/* Verify Button */} +
+

+ 2 + Verify connection +

+ +

+ After saving your DNS settings, click verify. DNS changes can take 5-15 minutes to propagate. +

+ + {error && ( +
+ + {error} +
+ )} + +
+ + +
+
+
+ )} + + {/* STEP 3: Success */} + {step === 3 && ( +
+
+ +
+ +
+

Yield Activated!

+

+ {selectedDomain} is now earning passive income. +

+
+ +
+
What happens now?
+
    +
  • + + Visitors to {selectedDomain} see your AI-generated landing page +
  • +
  • + + When they click the CTA, you earn revenue +
  • +
  • + + Track clicks and earnings in your Yield dashboard +
  • +
+
+ + +
)}