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() {
دامنه پلتفرم
+Platform Domain
{app.subdomain}.{domainInfo?.platformDomain || 'apps.cloudhost.ir'}
دامنه اختصاصی
+Custom Domain
{app.customDomain}
-
دامنه اختصاصی — در انتظار تأیید DNS
+Custom Domain — Pending DNS Verification
{app.customDomain}
- {step} -
- ))} +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
+CNAME Target:
-
- این هزینه در محاسبه کلی هزینهها در نظر گرفته میشود. + This fee is included in the total cost calculation.
- وبسایت را روی دامنه خود ببینید (با SSL رایگان) + Use your own domain with free SSL {domainPriceData && domainPriceData.monthlyPrice > 0 && ( - — {domainPriceData.monthlyPrice.toLocaleString('fa-IR')} تومان/ماه + — {domainPriceData.monthlyPrice.toLocaleString('en-US')} Toman/mo )}
- بعد از دیپلوی، باید رکورد DNS دامنه خود را تنظیم کنید. راهنمای کامل در صفحه جزئیات اپلیکیشن نمایش داده میشود. +
+ After deployment, you will need to configure your DNS records. Full instructions will be shown on the application detail page.