From 04b2f040e070e504fa8e6f60930e8ab966c9749b Mon Sep 17 00:00:00 2001 From: keyhan Date: Thu, 18 Jun 2026 14:31:45 +0330 Subject: [PATCH] feat(frontend): show active app domain with click-to-copy Surface the domain the app is actually reachable on right now (verified custom domain, else the platform subdomain) as a single "Application Domain" field. Clicking it copies the address to the clipboard and shows a "Copied" toast. Centralizes the active-domain logic so the page header and the verified-custom-domain block stay in sync, and always loads domain-info so the platform host is correct even without a custom domain. Co-Authored-By: Claude Opus 4.8 --- .../app/[lang]/dashboard/apps/[id]/page.tsx | 56 ++++++++++++++----- frontend/src/i18n/dictionaries/en.ts | 1 + frontend/src/i18n/dictionaries/fa.ts | 1 + 3 files changed, 44 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/[lang]/dashboard/apps/[id]/page.tsx b/frontend/src/app/[lang]/dashboard/apps/[id]/page.tsx index e48befc..7d428ef 100644 --- a/frontend/src/app/[lang]/dashboard/apps/[id]/page.tsx +++ b/frontend/src/app/[lang]/dashboard/apps/[id]/page.tsx @@ -303,7 +303,9 @@ export default function AppDetailPage() { }>({ queryKey: ['domain-info', appId], queryFn: () => api.get(`/applications/${appId}/domain`).then((r) => r.data), - enabled: showDomainSetup || (!!app && (app.customDomainStatus === 'pending_dns' || app.customDomainStatus === 'verified')), + // Always load: we surface the active app domain (platform host included) even + // when no custom domain is configured, so the platform host must be available. + enabled: !!app, }); const { data: domainPriceData } = useQuery<{ monthlyPrice: number }>({ @@ -1045,6 +1047,18 @@ export default function AppDetailPage() { const pageLocked = isAnyDeleting; + // The host the app is actually reachable on right now: the verified custom + // domain when present, otherwise the platform-assigned subdomain. Single source + // of truth so every place that shows "the app's domain" stays in sync. + const platformHost = `${app.subdomain}.${domainInfo?.platformDomain || 'apps.cloudhost.ir'}`; + const currentDomain = + app.customDomain && app.customDomainStatus === 'verified' ? app.customDomain : platformHost; + + const copyDomain = (domain: string) => { + navigator.clipboard.writeText(domain); + notify.success(ad.copied); + }; + return ( <> @@ -1067,10 +1081,7 @@ export default function AppDetailPage() {

{app.runtime}{app.runtimeVersion ? ` v${app.runtimeVersion}` : ''}{app.phpVersion ? ` — PHP ${app.phpVersion}` : ''} ·{' '} - {app.customDomain && app.customDomainStatus === 'verified' - ? {app.customDomain} - : {app.subdomain}.{domainInfo?.platformDomain || 'apps.cloudhost.ir'} - } + {currentDomain}

@@ -1604,16 +1615,24 @@ export default function AppDetailPage() { )} - {/* Platform domain (always shown) */} + {/* Application domain — the host the app is reachable on right now + (verified custom domain, else the platform subdomain). Click to copy. */}
-
-
-

{ad.platformDomain}

-

- {app.subdomain}.{domainInfo?.platformDomain || 'apps.cloudhost.ir'} -

+
+
+

{ad.appDomain}

+
- {ad.active} + {ad.active}
@@ -1623,7 +1642,16 @@ export default function AppDetailPage() {

{ad.customDomain}

-

{app.customDomain}

+

SSL Active — Verified on{' '} {app.customDomainVerifiedAt ? new Date(app.customDomainVerifiedAt).toLocaleString(locale) : ''} diff --git a/frontend/src/i18n/dictionaries/en.ts b/frontend/src/i18n/dictionaries/en.ts index afb1dbe..baaa53c 100644 --- a/frontend/src/i18n/dictionaries/en.ts +++ b/frontend/src/i18n/dictionaries/en.ts @@ -1286,6 +1286,7 @@ const en: Dictionary = { uploadToStart: 'Upload source code and click Deploy to get started', sourceCode: 'Source Code', replace: 'Replace', uploadSourceClick: 'Upload source code and click Deploy to get started', customDomain: 'Custom Domain', addCustomDomain: 'Add Custom Domain', setUpCustomDomain: 'Set Up Custom Domain', + appDomain: 'Application Domain', copyDomainHint: 'Click to copy', platformDomain: 'Platform Domain', domainPlaceholder: 'example.com or www.example.com', dnsSetupGuide: 'DNS Setup Guide', removeCustomDomain: 'Remove Custom Domain', resourcesScaling: 'Resources & Scaling', monitor: 'Monitor', hide: 'Hide', show: 'Show', diff --git a/frontend/src/i18n/dictionaries/fa.ts b/frontend/src/i18n/dictionaries/fa.ts index ba81b62..9b0fb3b 100644 --- a/frontend/src/i18n/dictionaries/fa.ts +++ b/frontend/src/i18n/dictionaries/fa.ts @@ -1291,6 +1291,7 @@ const fa = { sourceCode: 'سورس‌کد', replace: 'جایگزینی', uploadSourceClick: 'سورس‌کد را آپلود و روی «انتشار» بزن تا شروع کنی', // domain customDomain: 'دامنهٔ اختصاصی', addCustomDomain: 'افزودن دامنهٔ اختصاصی', setUpCustomDomain: 'راه‌اندازی دامنهٔ اختصاصی', + appDomain: 'دامنهٔ اپلیکیشن', copyDomainHint: 'برای کپی کلیک کن', platformDomain: 'دامنهٔ پلتفرم', domainPlaceholder: 'example.com یا www.example.com', dnsSetupGuide: 'راهنمای تنظیم DNS', removeCustomDomain: 'حذف دامنهٔ اختصاصی', // resources