From 30ccfa4ee2a07713e02a6b96b8b8d1f703c56e76 Mon Sep 17 00:00:00 2001 From: keyhan Date: Thu, 14 May 2026 00:49:20 +0330 Subject: [PATCH] fix: update cost estimate on domain toggle, switch to English, fix toggle overflow - Cost calculation now reads custom domain price from PlatformSetting as fallback when no CUSTOM_DOMAIN_ADDON pricing rule exists - Convert all custom domain UI text from Persian to English - Fix toggle switch overflow by adding shrink-0, min-w-0, and proper absolute positioning Co-authored-by: Cursor --- backend/src/applications/domain.service.ts | 14 +-- backend/src/billing/billing.service.ts | 12 +++ frontend/src/app/dashboard/apps/[id]/page.tsx | 86 ++++++++++--------- frontend/src/app/dashboard/deploy/page.tsx | 25 +++--- 4 files changed, 77 insertions(+), 60 deletions(-) diff --git a/backend/src/applications/domain.service.ts b/backend/src/applications/domain.service.ts index 341791a..9f96028 100644 --- a/backend/src/applications/domain.service.ts +++ b/backend/src/applications/domain.service.ts @@ -145,15 +145,15 @@ export class DomainService { const fullPlatformUrl = `${app.subdomain}.${platformDomain}`; const instructions = [ - `1. وارد پنل مدیریت دامنه خود شوید (مانند Cloudflare، Namecheap، GoDaddy و غیره)`, - `2. به بخش مدیریت DNS بروید`, - `3. یک رکورد CNAME اضافه کنید:`, - ` - Name/Host: @ یا www (بسته به دامنه‌تان)`, + `1. Log in to your domain registrar (e.g. Cloudflare, Namecheap, GoDaddy)`, + `2. Go to DNS management for your domain`, + `3. Add a CNAME record:`, + ` - Name/Host: @ or www (depending on your domain)`, ` - Type: CNAME`, ` - Value/Target: ${fullPlatformUrl}`, - `4. اگر از دامنه اصلی (root domain) بدون www استفاده می‌کنید، برخی ثبت‌کنندگان از CNAME flattening پشتیبانی می‌کنند (مانند Cloudflare). در غیر این صورت از www استفاده کنید.`, - `5. بین ۵ تا ۳۰ دقیقه صبر کنید تا DNS منتشر شود (تا ۴۸ ساعت ممکن است طول بکشد)`, - `6. دکمه "تأیید DNS" را بزنید`, + `4. If using a root domain (without www), some registrars support CNAME flattening (e.g. Cloudflare). Otherwise use www.`, + `5. Wait 5-30 minutes for DNS propagation (may take up to 48 hours)`, + `6. Click the "Verify DNS" button`, ]; return { diff --git a/backend/src/billing/billing.service.ts b/backend/src/billing/billing.service.ts index d820ba8..6302565 100644 --- a/backend/src/billing/billing.service.ts +++ b/backend/src/billing/billing.service.ts @@ -203,6 +203,18 @@ export class BillingService { } } + // If custom domain is enabled but no CUSTOM_DOMAIN_ADDON rule exists, use PlatformSetting price + if (hasCustomDomain && !breakdown.some((b) => b.label === 'Custom domain + SSL')) { + const setting = await this.settingsRepo.findOne({ where: { key: 'custom_domain_monthly_price_toman' } }); + const monthlyPrice = setting ? Number(setting.value) : 0; + if (monthlyPrice > 0) { + const hourly = Math.round(monthlyPrice / 720); + const yearly = monthlyPrice * 12; + breakdown.push({ label: 'Custom domain + SSL', hourly, monthly: monthlyPrice, yearly }); + totalBase += baseCycle === 'monthly' ? monthlyPrice : baseCycle === 'hourly' ? hourly : yearly; + } + } + const hourlyTotal = baseCycle === 'hourly' ? totalBase : baseCycle === 'monthly' ? totalBase / 720 : totalBase / 8640; const monthlyTotal = baseCycle === 'monthly' ? totalBase : baseCycle === 'hourly' ? totalBase * 720 : totalBase / 12; const yearlyTotal = baseCycle === 'yearly' ? totalBase : baseCycle === 'monthly' ? totalBase * 12 : totalBase * 8640; diff --git a/frontend/src/app/dashboard/apps/[id]/page.tsx b/frontend/src/app/dashboard/apps/[id]/page.tsx index 41d341f..f82c02a 100644 --- a/frontend/src/app/dashboard/apps/[id]/page.tsx +++ b/frontend/src/app/dashboard/apps/[id]/page.tsx @@ -252,36 +252,36 @@ export default function AppDetailPage() { const setDomainMutation = useMutation({ mutationFn: (domain: string) => api.post(`/applications/${appId}/domain`, { domain }), onSuccess: () => { - toast.success('دامنه تنظیم شد. لطفاً رکورد DNS را اضافه کنید.'); + toast.success('Domain set. Please configure your DNS records.'); queryClient.invalidateQueries({ queryKey: ['application', appId] }); refetchDomainInfo(); setCustomDomainInput(''); }, - onError: (err: any) => toast.error(err.response?.data?.message || 'خطا در تنظیم دامنه'), + onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to set domain'), }); const verifyDnsMutation = useMutation({ mutationFn: () => api.post(`/applications/${appId}/domain/verify`), onSuccess: (res) => { if (res.data.verified) { - toast.success('دامنه با موفقیت تأیید شد!'); + toast.success('Domain verified successfully!'); } else { - toast.warning(res.data.message || 'DNS هنوز آماده نیست. لطفاً بعداً تلاش کنید.'); + toast.warning(res.data.message || 'DNS is not ready yet. Please try again later.'); } queryClient.invalidateQueries({ queryKey: ['application', appId] }); refetchDomainInfo(); }, - onError: (err: any) => toast.error(err.response?.data?.message || 'خطا در تأیید DNS'), + onError: (err: any) => toast.error(err.response?.data?.message || 'DNS verification failed'), }); const removeDomainMutation = useMutation({ mutationFn: () => api.delete(`/applications/${appId}/domain`), onSuccess: () => { - toast.success('دامنه اختصاصی حذف شد'); + toast.success('Custom domain removed'); queryClient.invalidateQueries({ queryKey: ['application', appId] }); refetchDomainInfo(); }, - onError: (err: any) => toast.error(err.response?.data?.message || 'خطا در حذف دامنه'), + onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to remove domain'), }); // ─── Snapshots ────────────────────────────────────── @@ -1268,14 +1268,14 @@ export default function AppDetailPage() {

- دامنه + Domain

{!showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && ( )}
@@ -1284,12 +1284,12 @@ export default function AppDetailPage() {
-

دامنه پلتفرم

+

Platform Domain

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

- فعال + Active
@@ -1298,19 +1298,19 @@ export default function AppDetailPage() {
-

دامنه اختصاصی

+

Custom Domain

{app.customDomain}

- SSL فعال — تأیید شده در{' '} - {app.customDomainVerifiedAt ? new Date(app.customDomainVerifiedAt).toLocaleString('fa-IR') : ''} + SSL Active — Verified on{' '} + {app.customDomainVerifiedAt ? new Date(app.customDomainVerifiedAt).toLocaleString() : ''}

@@ -1329,7 +1329,7 @@ export default function AppDetailPage() {
-

دامنه اختصاصی — در انتظار تأیید DNS

+

Custom Domain — Pending DNS Verification

{app.customDomain}

@@ -1338,29 +1338,35 @@ export default function AppDetailPage() { disabled={verifyDnsMutation.isPending} className="btn-primary text-sm" > - {verifyDnsMutation.isPending ? 'در حال بررسی...' : 'تأیید DNS'} + {verifyDnsMutation.isPending ? 'Checking...' : 'Verify DNS'}
{/* DNS Instructions */} - {domainInfo?.instructions && ( -
-

راهنمای تنظیم DNS

-
- {domainInfo.instructions.map((step, i) => ( -

- {step} -

- ))} +
+

DNS Setup Guide

+
+

1. Log in to your domain registrar (e.g. Cloudflare, Namecheap, GoDaddy)

+

2. Go to DNS management for your domain

+

3. Add a CNAME record:

+
+

Name/Host: @ or www

+

Type: CNAME

+

Value: {domainInfo?.fullPlatformUrl || `${app.subdomain}.apps.cloudhost.ir`}

+

4. If using a root domain (without www), use a registrar that supports CNAME flattening (e.g. Cloudflare), or use www instead.

+

5. Wait 5–30 minutes for DNS propagation (up to 48 hours in some cases)

+

6. Click the "Verify DNS" button above

+
+ {domainInfo?.fullPlatformUrl && (

CNAME Target:

@@ -1370,7 +1376,7 @@ export default function AppDetailPage() {
-
- )} + )} +
)} {/* Domain setup form */} {showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && (
-

تنظیم دامنه اختصاصی

+

Set Up Custom Domain

{domainPriceData && domainPriceData.monthlyPrice > 0 && (

- - هزینه دامنه اختصاصی: {domainPriceData.monthlyPrice.toLocaleString('fa-IR')} تومان / ماهانه + + Custom domain fee: {domainPriceData.monthlyPrice.toLocaleString('en-US')} Toman / month

- این هزینه در محاسبه کلی هزینه‌ها در نظر گرفته می‌شود. + This fee is included in the total cost calculation.

)} -
+
- {setDomainMutation.isPending ? 'در حال ثبت...' : 'ثبت دامنه'} + {setDomainMutation.isPending ? 'Setting up...' : 'Set Domain'}
diff --git a/frontend/src/app/dashboard/deploy/page.tsx b/frontend/src/app/dashboard/deploy/page.tsx index e8a15eb..fec5ed3 100644 --- a/frontend/src/app/dashboard/deploy/page.tsx +++ b/frontend/src/app/dashboard/deploy/page.tsx @@ -1939,17 +1939,17 @@ export default function DeployPage() { {/* Custom Domain Option */}
-
-
-
+
+
+
-
-

دامنه اختصاصی

+
+

Custom Domain

- وبسایت را روی دامنه خود ببینید (با SSL رایگان) + Use your own domain with free SSL {domainPriceData && domainPriceData.monthlyPrice > 0 && ( - — {domainPriceData.monthlyPrice.toLocaleString('fa-IR')} تومان/ماه + — {domainPriceData.monthlyPrice.toLocaleString('en-US')} Toman/mo )}

@@ -1957,24 +1957,23 @@ export default function DeployPage() {
{enableCustomDomain && (
- + setCustomDomainInput(e.target.value)} placeholder="example.com or www.example.com" className="input-field w-full font-mono text-sm" /> -

- بعد از دیپلوی، باید رکورد DNS دامنه خود را تنظیم کنید. راهنمای کامل در صفحه جزئیات اپلیکیشن نمایش داده می‌شود. +

+ After deployment, you will need to configure your DNS records. Full instructions will be shown on the application detail page.

)}