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 <cursoragent@cursor.com>
This commit is contained in:
@@ -145,15 +145,15 @@ export class DomainService {
|
|||||||
const fullPlatformUrl = `${app.subdomain}.${platformDomain}`;
|
const fullPlatformUrl = `${app.subdomain}.${platformDomain}`;
|
||||||
|
|
||||||
const instructions = [
|
const instructions = [
|
||||||
`1. وارد پنل مدیریت دامنه خود شوید (مانند Cloudflare، Namecheap، GoDaddy و غیره)`,
|
`1. Log in to your domain registrar (e.g. Cloudflare, Namecheap, GoDaddy)`,
|
||||||
`2. به بخش مدیریت DNS بروید`,
|
`2. Go to DNS management for your domain`,
|
||||||
`3. یک رکورد CNAME اضافه کنید:`,
|
`3. Add a CNAME record:`,
|
||||||
` - Name/Host: @ یا www (بسته به دامنهتان)`,
|
` - Name/Host: @ or www (depending on your domain)`,
|
||||||
` - Type: CNAME`,
|
` - Type: CNAME`,
|
||||||
` - Value/Target: ${fullPlatformUrl}`,
|
` - Value/Target: ${fullPlatformUrl}`,
|
||||||
`4. اگر از دامنه اصلی (root domain) بدون www استفاده میکنید، برخی ثبتکنندگان از CNAME flattening پشتیبانی میکنند (مانند Cloudflare). در غیر این صورت از www استفاده کنید.`,
|
`4. If using a root domain (without www), some registrars support CNAME flattening (e.g. Cloudflare). Otherwise use www.`,
|
||||||
`5. بین ۵ تا ۳۰ دقیقه صبر کنید تا DNS منتشر شود (تا ۴۸ ساعت ممکن است طول بکشد)`,
|
`5. Wait 5-30 minutes for DNS propagation (may take up to 48 hours)`,
|
||||||
`6. دکمه "تأیید DNS" را بزنید`,
|
`6. Click the "Verify DNS" button`,
|
||||||
];
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -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 hourlyTotal = baseCycle === 'hourly' ? totalBase : baseCycle === 'monthly' ? totalBase / 720 : totalBase / 8640;
|
||||||
const monthlyTotal = baseCycle === 'monthly' ? totalBase : baseCycle === 'hourly' ? totalBase * 720 : totalBase / 12;
|
const monthlyTotal = baseCycle === 'monthly' ? totalBase : baseCycle === 'hourly' ? totalBase * 720 : totalBase / 12;
|
||||||
const yearlyTotal = baseCycle === 'yearly' ? totalBase : baseCycle === 'monthly' ? totalBase * 12 : totalBase * 8640;
|
const yearlyTotal = baseCycle === 'yearly' ? totalBase : baseCycle === 'monthly' ? totalBase * 12 : totalBase * 8640;
|
||||||
|
|||||||
@@ -252,36 +252,36 @@ export default function AppDetailPage() {
|
|||||||
const setDomainMutation = useMutation({
|
const setDomainMutation = useMutation({
|
||||||
mutationFn: (domain: string) => api.post(`/applications/${appId}/domain`, { domain }),
|
mutationFn: (domain: string) => api.post(`/applications/${appId}/domain`, { domain }),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('دامنه تنظیم شد. لطفاً رکورد DNS را اضافه کنید.');
|
toast.success('Domain set. Please configure your DNS records.');
|
||||||
queryClient.invalidateQueries({ queryKey: ['application', appId] });
|
queryClient.invalidateQueries({ queryKey: ['application', appId] });
|
||||||
refetchDomainInfo();
|
refetchDomainInfo();
|
||||||
setCustomDomainInput('');
|
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({
|
const verifyDnsMutation = useMutation({
|
||||||
mutationFn: () => api.post(`/applications/${appId}/domain/verify`),
|
mutationFn: () => api.post(`/applications/${appId}/domain/verify`),
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
if (res.data.verified) {
|
if (res.data.verified) {
|
||||||
toast.success('دامنه با موفقیت تأیید شد!');
|
toast.success('Domain verified successfully!');
|
||||||
} else {
|
} 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] });
|
queryClient.invalidateQueries({ queryKey: ['application', appId] });
|
||||||
refetchDomainInfo();
|
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({
|
const removeDomainMutation = useMutation({
|
||||||
mutationFn: () => api.delete(`/applications/${appId}/domain`),
|
mutationFn: () => api.delete(`/applications/${appId}/domain`),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast.success('دامنه اختصاصی حذف شد');
|
toast.success('Custom domain removed');
|
||||||
queryClient.invalidateQueries({ queryKey: ['application', appId] });
|
queryClient.invalidateQueries({ queryKey: ['application', appId] });
|
||||||
refetchDomainInfo();
|
refetchDomainInfo();
|
||||||
},
|
},
|
||||||
onError: (err: any) => toast.error(err.response?.data?.message || 'خطا در حذف دامنه'),
|
onError: (err: any) => toast.error(err.response?.data?.message || 'Failed to remove domain'),
|
||||||
});
|
});
|
||||||
|
|
||||||
// ─── Snapshots ──────────────────────────────────────
|
// ─── Snapshots ──────────────────────────────────────
|
||||||
@@ -1268,14 +1268,14 @@ export default function AppDetailPage() {
|
|||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="flex items-center justify-between mb-4">
|
<div className="flex items-center justify-between mb-4">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
<h2 className="text-lg font-semibold text-gray-900 flex items-center gap-2">
|
||||||
<Globe className="w-5 h-5" /> دامنه
|
<Globe className="w-5 h-5" /> Domain
|
||||||
</h2>
|
</h2>
|
||||||
{!showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && (
|
{!showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowDomainSetup(true)}
|
onClick={() => setShowDomainSetup(true)}
|
||||||
className="btn-primary text-sm"
|
className="btn-primary text-sm"
|
||||||
>
|
>
|
||||||
افزودن دامنه اختصاصی
|
Add Custom Domain
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -1284,12 +1284,12 @@ export default function AppDetailPage() {
|
|||||||
<div className="bg-gray-50 rounded-xl p-4 mb-4">
|
<div className="bg-gray-50 rounded-xl p-4 mb-4">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-gray-500 mb-1">دامنه پلتفرم</p>
|
<p className="text-xs text-gray-500 mb-1">Platform Domain</p>
|
||||||
<p className="text-sm font-mono font-medium text-gray-800">
|
<p className="text-sm font-mono font-medium text-gray-800">
|
||||||
{app.subdomain}.{domainInfo?.platformDomain || 'apps.cloudhost.ir'}
|
{app.subdomain}.{domainInfo?.platformDomain || 'apps.cloudhost.ir'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span className="badge badge-green text-xs">فعال</span>
|
<span className="badge badge-green text-xs">Active</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1298,19 +1298,19 @@ export default function AppDetailPage() {
|
|||||||
<div className="bg-emerald-50 rounded-xl p-4 mb-4 border border-emerald-200">
|
<div className="bg-emerald-50 rounded-xl p-4 mb-4 border border-emerald-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-emerald-600 mb-1">دامنه اختصاصی</p>
|
<p className="text-xs text-emerald-600 mb-1">Custom Domain</p>
|
||||||
<p className="text-sm font-mono font-medium text-emerald-800">{app.customDomain}</p>
|
<p className="text-sm font-mono font-medium text-emerald-800">{app.customDomain}</p>
|
||||||
<p className="text-xs text-emerald-500 mt-1">
|
<p className="text-xs text-emerald-500 mt-1">
|
||||||
<CheckCircle className="w-3 h-3 inline" /> SSL فعال — تأیید شده در{' '}
|
<CheckCircle className="w-3 h-3 inline" /> SSL Active — Verified on{' '}
|
||||||
{app.customDomainVerifiedAt ? new Date(app.customDomainVerifiedAt).toLocaleString('fa-IR') : ''}
|
{app.customDomainVerifiedAt ? new Date(app.customDomainVerifiedAt).toLocaleString() : ''}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const ok = await confirm({
|
const ok = await confirm({
|
||||||
title: 'حذف دامنه اختصاصی',
|
title: 'Remove Custom Domain',
|
||||||
message: `آیا مطمئن هستید که میخواهید دامنه "${app.customDomain}" را حذف کنید؟ وبسایت فقط از طریق دامنه پلتفرم قابل دسترسی خواهد بود.`,
|
message: `Are you sure you want to remove "${app.customDomain}"? The website will only be accessible via the platform domain.`,
|
||||||
confirmText: 'حذف',
|
confirmText: 'Remove',
|
||||||
variant: 'danger',
|
variant: 'danger',
|
||||||
});
|
});
|
||||||
if (ok) removeDomainMutation.mutate();
|
if (ok) removeDomainMutation.mutate();
|
||||||
@@ -1318,7 +1318,7 @@ export default function AppDetailPage() {
|
|||||||
disabled={removeDomainMutation.isPending}
|
disabled={removeDomainMutation.isPending}
|
||||||
className="text-sm px-3 py-1.5 rounded-lg text-red-600 hover:bg-red-50 border border-red-200 transition-colors"
|
className="text-sm px-3 py-1.5 rounded-lg text-red-600 hover:bg-red-50 border border-red-200 transition-colors"
|
||||||
>
|
>
|
||||||
{removeDomainMutation.isPending ? 'در حال حذف...' : 'حذف دامنه'}
|
{removeDomainMutation.isPending ? 'Removing...' : 'Remove Domain'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1329,7 +1329,7 @@ export default function AppDetailPage() {
|
|||||||
<div className="bg-amber-50 rounded-xl p-4 mb-4 border border-amber-200">
|
<div className="bg-amber-50 rounded-xl p-4 mb-4 border border-amber-200">
|
||||||
<div className="flex items-center justify-between mb-3">
|
<div className="flex items-center justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<p className="text-xs text-amber-600 mb-1">دامنه اختصاصی — در انتظار تأیید DNS</p>
|
<p className="text-xs text-amber-600 mb-1">Custom Domain — Pending DNS Verification</p>
|
||||||
<p className="text-sm font-mono font-medium text-amber-800">{app.customDomain}</p>
|
<p className="text-sm font-mono font-medium text-amber-800">{app.customDomain}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
@@ -1338,29 +1338,35 @@ export default function AppDetailPage() {
|
|||||||
disabled={verifyDnsMutation.isPending}
|
disabled={verifyDnsMutation.isPending}
|
||||||
className="btn-primary text-sm"
|
className="btn-primary text-sm"
|
||||||
>
|
>
|
||||||
{verifyDnsMutation.isPending ? 'در حال بررسی...' : 'تأیید DNS'}
|
{verifyDnsMutation.isPending ? 'Checking...' : 'Verify DNS'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => removeDomainMutation.mutate()}
|
onClick={() => removeDomainMutation.mutate()}
|
||||||
disabled={removeDomainMutation.isPending}
|
disabled={removeDomainMutation.isPending}
|
||||||
className="text-sm px-3 py-1.5 rounded-lg text-red-600 hover:bg-red-50 border border-red-200 transition-colors"
|
className="text-sm px-3 py-1.5 rounded-lg text-red-600 hover:bg-red-50 border border-red-200 transition-colors"
|
||||||
>
|
>
|
||||||
لغو
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* DNS Instructions */}
|
{/* DNS Instructions */}
|
||||||
{domainInfo?.instructions && (
|
|
||||||
<div className="bg-white rounded-lg p-4 border border-amber-100">
|
<div className="bg-white rounded-lg p-4 border border-amber-100">
|
||||||
<h4 className="text-sm font-semibold text-gray-800 mb-3">راهنمای تنظیم DNS</h4>
|
<h4 className="text-sm font-semibold text-gray-800 mb-3">DNS Setup Guide</h4>
|
||||||
<div className="space-y-2 text-sm text-gray-600" dir="rtl">
|
<div className="space-y-2.5 text-sm text-gray-600">
|
||||||
{domainInfo.instructions.map((step, i) => (
|
<p>1. Log in to your domain registrar (e.g. Cloudflare, Namecheap, GoDaddy)</p>
|
||||||
<p key={i} className={step.startsWith(' ') ? 'pr-4 text-xs font-mono bg-gray-50 rounded px-2 py-1' : ''}>
|
<p>2. Go to DNS management for your domain</p>
|
||||||
{step}
|
<p>3. Add a <strong>CNAME</strong> record:</p>
|
||||||
</p>
|
<div className="pl-4 space-y-1">
|
||||||
))}
|
<p className="text-xs font-mono bg-gray-50 rounded px-2 py-1">Name/Host: <strong>@</strong> or <strong>www</strong></p>
|
||||||
|
<p className="text-xs font-mono bg-gray-50 rounded px-2 py-1">Type: <strong>CNAME</strong></p>
|
||||||
|
<p className="text-xs font-mono bg-gray-50 rounded px-2 py-1">Value: <strong>{domainInfo?.fullPlatformUrl || `${app.subdomain}.apps.cloudhost.ir`}</strong></p>
|
||||||
</div>
|
</div>
|
||||||
|
<p>4. If using a root domain (without www), use a registrar that supports CNAME flattening (e.g. Cloudflare), or use <code className="bg-gray-100 px-1 rounded">www</code> instead.</p>
|
||||||
|
<p>5. Wait 5–30 minutes for DNS propagation (up to 48 hours in some cases)</p>
|
||||||
|
<p>6. Click the <strong>"Verify DNS"</strong> button above</p>
|
||||||
|
</div>
|
||||||
|
{domainInfo?.fullPlatformUrl && (
|
||||||
<div className="mt-4 bg-blue-50 rounded-lg p-3 border border-blue-100">
|
<div className="mt-4 bg-blue-50 rounded-lg p-3 border border-blue-100">
|
||||||
<p className="text-xs text-blue-700 font-medium mb-1">CNAME Target:</p>
|
<p className="text-xs text-blue-700 font-medium mb-1">CNAME Target:</p>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@@ -1370,7 +1376,7 @@ export default function AppDetailPage() {
|
|||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(domainInfo.fullPlatformUrl);
|
navigator.clipboard.writeText(domainInfo.fullPlatformUrl);
|
||||||
toast.success('کپی شد!');
|
toast.success('Copied!');
|
||||||
}}
|
}}
|
||||||
className="text-blue-600 hover:text-blue-800 p-1"
|
className="text-blue-600 hover:text-blue-800 p-1"
|
||||||
>
|
>
|
||||||
@@ -1378,27 +1384,27 @@ export default function AppDetailPage() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Domain setup form */}
|
{/* Domain setup form */}
|
||||||
{showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && (
|
{showDomainSetup && (!app.customDomain || app.customDomainStatus === 'none') && (
|
||||||
<div className="bg-gray-50 rounded-xl p-4 border border-gray-200">
|
<div className="bg-gray-50 rounded-xl p-4 border border-gray-200">
|
||||||
<h4 className="text-sm font-semibold text-gray-800 mb-3">تنظیم دامنه اختصاصی</h4>
|
<h4 className="text-sm font-semibold text-gray-800 mb-3">Set Up Custom Domain</h4>
|
||||||
{domainPriceData && domainPriceData.monthlyPrice > 0 && (
|
{domainPriceData && domainPriceData.monthlyPrice > 0 && (
|
||||||
<div className="bg-blue-50 rounded-lg p-3 mb-4 border border-blue-100">
|
<div className="bg-blue-50 rounded-lg p-3 mb-4 border border-blue-100">
|
||||||
<p className="text-sm text-blue-700">
|
<p className="text-sm text-blue-700">
|
||||||
<CreditCard className="w-4 h-4 inline ml-1" />
|
<CreditCard className="w-4 h-4 inline mr-1" />
|
||||||
هزینه دامنه اختصاصی: <strong>{domainPriceData.monthlyPrice.toLocaleString('fa-IR')} تومان / ماهانه</strong>
|
Custom domain fee: <strong>{domainPriceData.monthlyPrice.toLocaleString('en-US')} Toman / month</strong>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-blue-500 mt-1">
|
<p className="text-xs text-blue-500 mt-1">
|
||||||
این هزینه در محاسبه کلی هزینهها در نظر گرفته میشود.
|
This fee is included in the total cost calculation.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-2" dir="ltr">
|
<div className="flex gap-2">
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={customDomainInput}
|
value={customDomainInput}
|
||||||
@@ -1413,13 +1419,13 @@ export default function AppDetailPage() {
|
|||||||
disabled={!customDomainInput.trim() || setDomainMutation.isPending}
|
disabled={!customDomainInput.trim() || setDomainMutation.isPending}
|
||||||
className="btn-primary text-sm disabled:opacity-50"
|
className="btn-primary text-sm disabled:opacity-50"
|
||||||
>
|
>
|
||||||
{setDomainMutation.isPending ? 'در حال ثبت...' : 'ثبت دامنه'}
|
{setDomainMutation.isPending ? 'Setting up...' : 'Set Domain'}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => { setShowDomainSetup(false); setCustomDomainInput(''); }}
|
onClick={() => { setShowDomainSetup(false); setCustomDomainInput(''); }}
|
||||||
className="btn-secondary text-sm"
|
className="btn-secondary text-sm"
|
||||||
>
|
>
|
||||||
انصراف
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1939,17 +1939,17 @@ export default function DeployPage() {
|
|||||||
|
|
||||||
{/* Custom Domain Option */}
|
{/* Custom Domain Option */}
|
||||||
<div className="bg-white rounded-xl p-5 border border-gray-200">
|
<div className="bg-white rounded-xl p-5 border border-gray-200">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<div className="w-10 h-10 rounded-xl bg-purple-50 flex items-center justify-center">
|
<div className="w-10 h-10 rounded-xl bg-purple-50 flex items-center justify-center shrink-0">
|
||||||
<Globe className="w-5 h-5 text-purple-600" />
|
<Globe className="w-5 h-5 text-purple-600" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div className="min-w-0">
|
||||||
<h3 className="text-sm font-semibold text-gray-800">دامنه اختصاصی</h3>
|
<h3 className="text-sm font-semibold text-gray-800">Custom Domain</h3>
|
||||||
<p className="text-xs text-gray-500">
|
<p className="text-xs text-gray-500">
|
||||||
وبسایت را روی دامنه خود ببینید (با SSL رایگان)
|
Use your own domain with free SSL
|
||||||
{domainPriceData && domainPriceData.monthlyPrice > 0 && (
|
{domainPriceData && domainPriceData.monthlyPrice > 0 && (
|
||||||
<span className="text-purple-600 font-medium"> — {domainPriceData.monthlyPrice.toLocaleString('fa-IR')} تومان/ماه</span>
|
<span className="text-purple-600 font-medium"> — {domainPriceData.monthlyPrice.toLocaleString('en-US')} Toman/mo</span>
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -1957,24 +1957,23 @@ export default function DeployPage() {
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setEnableCustomDomain(!enableCustomDomain)}
|
onClick={() => setEnableCustomDomain(!enableCustomDomain)}
|
||||||
className={`relative w-12 h-6 rounded-full transition-colors ${enableCustomDomain ? 'bg-purple-600' : 'bg-gray-300'}`}
|
className={`relative shrink-0 w-11 h-6 rounded-full transition-colors ${enableCustomDomain ? 'bg-purple-600' : 'bg-gray-300'}`}
|
||||||
>
|
>
|
||||||
<span className={`absolute top-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${enableCustomDomain ? 'translate-x-6' : 'translate-x-0.5'}`} />
|
<span className={`block absolute top-0.5 left-0.5 w-5 h-5 bg-white rounded-full shadow transition-transform ${enableCustomDomain ? 'translate-x-5' : 'translate-x-0'}`} />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
{enableCustomDomain && (
|
{enableCustomDomain && (
|
||||||
<div className="mt-4 pt-4 border-t border-gray-100">
|
<div className="mt-4 pt-4 border-t border-gray-100">
|
||||||
<label className="block text-sm font-medium text-gray-700 mb-2">آدرس دامنه</label>
|
<label className="block text-sm font-medium text-gray-700 mb-2">Domain Address</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
dir="ltr"
|
|
||||||
value={customDomainInput}
|
value={customDomainInput}
|
||||||
onChange={(e) => setCustomDomainInput(e.target.value)}
|
onChange={(e) => setCustomDomainInput(e.target.value)}
|
||||||
placeholder="example.com or www.example.com"
|
placeholder="example.com or www.example.com"
|
||||||
className="input-field w-full font-mono text-sm"
|
className="input-field w-full font-mono text-sm"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-400 mt-2" dir="rtl">
|
<p className="text-xs text-gray-400 mt-2">
|
||||||
بعد از دیپلوی، باید رکورد DNS دامنه خود را تنظیم کنید. راهنمای کامل در صفحه جزئیات اپلیکیشن نمایش داده میشود.
|
After deployment, you will need to configure your DNS records. Full instructions will be shown on the application detail page.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user