- When checked, upserts Arvan records: @ ANAME → ns.meshkee.com, plus www,
- business, customer, and api CNAMEs.
+ {editDnsProvider === 'cloudflare'
+ ? 'When checked, upserts Cloudflare records (DNS-only): @ CNAME → ns.meshkee.com, plus www, business, customer, and api CNAMEs.'
+ : 'When checked, upserts Arvan records: @ ANAME → ns.meshkee.com, plus www, business, customer, and api CNAMEs.'}
diff --git a/apps/super-admin/src/services/businessService.ts b/apps/super-admin/src/services/businessService.ts
index 2f48ed7..d3626cb 100644
--- a/apps/super-admin/src/services/businessService.ts
+++ b/apps/super-admin/src/services/businessService.ts
@@ -29,8 +29,10 @@ export interface UpdateBusinessPayload {
export interface AddDomainPayload {
host: string
isPrimary?: boolean
- /** When true, upsert Meshkee tenant records in Arvan DNS. */
+ /** When true, upsert Meshkee tenant records (@, www, business, customer, api). */
updateDns?: boolean
+ /** DNS host when updateDns is true. Default arvan. */
+ dnsProvider?: 'arvan' | 'cloudflare'
/** When set, provisions storefront deploy on the websites VM. */
gitRepoUrl?: string
}
@@ -41,8 +43,10 @@ export interface DisableBusinessPayload {
export interface UpdateDomainPayload {
host: string
- /** When true, upsert Meshkee tenant records in Arvan DNS. */
+ /** When true, upsert Meshkee tenant records (@, www, business, customer, api). */
updateDns?: boolean
+ /** DNS host when updateDns is true. Default arvan. */
+ dnsProvider?: 'arvan' | 'cloudflare'
/** When set, provisions storefront deploy on the websites VM. */
gitRepoUrl?: string
}
diff --git a/apps/super-admin/src/services/domainService.ts b/apps/super-admin/src/services/domainService.ts
index c88884e..96c09b4 100644
--- a/apps/super-admin/src/services/domainService.ts
+++ b/apps/super-admin/src/services/domainService.ts
@@ -17,8 +17,10 @@ export interface ListDomainsParams {
export interface UpdateDomainPayload {
host?: string
expiresAt?: string
- /** When true, upsert Meshkee tenant records in Arvan DNS. */
+ /** When true, upsert Meshkee tenant records (@, www, business, customer, api). */
updateDns?: boolean
+ /** DNS host when updateDns is true. Default arvan. */
+ dnsProvider?: 'arvan' | 'cloudflare'
/** When set, provisions storefront deploy on the websites VM. */
gitRepoUrl?: string
}
diff --git a/docs/PROJECT_CONTEXT.md b/docs/PROJECT_CONTEXT.md
index 8adc719..d93c126 100644
--- a/docs/PROJECT_CONTEXT.md
+++ b/docs/PROJECT_CONTEXT.md
@@ -138,11 +138,11 @@ Add to `/etc/hosts` (one line per tenant):
|------|------|
| `/login` | Login |
| `/` | Home |
-| `/businesses` | Businesses list (migrate-from-old + delete-data for portfolios/blogs(+news)/customers + categories; single-select). **Add/Edit domain** accepts optional Git repo URL → provisions storefront on websites VM and sets `deploy_slug`. Optional **Update DNS records** checkbox upserts Arvan DNS (`@` ANAME → `ns.meshkee.com`, `www` CNAME → apex, `business`/`customer`/`api` CNAMEs). |
+| `/businesses` | Businesses list (migrate-from-old + delete-data for portfolios/blogs(+news)/customers + categories; single-select). **Add/Edit domain** accepts optional Git repo URL → provisions storefront on websites VM and sets `deploy_slug`. Optional **Update DNS records** checkbox upserts tenant DNS (`@` → `ns.meshkee.com`, `www` CNAME → apex, `business`/`customer`/`api` CNAMEs) via **Arvan** or **Cloudflare** (switch; Cloudflare is DNS-only / not proxied). |
| `/businesses/:businessId/invoices` | Business invoices list |
| `/businesses/:businessId/invoices/new` | Issue invoice (full page) |
| `/users` | Users (business filter: Customer vs Manager → Admin/Editor/Viewer; Admin assignable by super-admin only; owners locked) |
-| `/websites` | Websites / domains (Deploy when `deploy_slug` set). Edit domain can optionally update Arvan DNS. Overflow ⋯ includes website/dashboard links, **AI prompts**, and remove. **Parked domains** button: alias hosts 301 to the main domain. Per alias, choose **Arvan** or **Cloudflare** for DNS (`@` + `www` only, not proxied); SSL is always Let's Encrypt on the websites VM. |
+| `/websites` | Websites / domains (Deploy when `deploy_slug` set). Edit domain can optionally update DNS (Arvan or Cloudflare, same tenant records as Businesses). Overflow ⋯ includes website/dashboard links, **AI prompts**, and remove. **Parked domains** button: alias hosts 301 to the main domain. Per alias, choose **Arvan** or **Cloudflare** for DNS (`@` + `www` only, not proxied); SSL is always Let's Encrypt on the websites VM. |
| `/templates` | Global AI prompt templates. Assign from Businesses or Websites ⋯ → **AI prompts** (add from library; public pack link). |
| `/settings` | Platform settings (invoice templates + item templates) |
| `/settings/invoice-templates/new` | Create invoice template |
diff --git a/packages/dashboard-ui/src/components/EmptyState.module.css b/packages/dashboard-ui/src/components/EmptyState.module.css
index 577803d..faf3463 100644
--- a/packages/dashboard-ui/src/components/EmptyState.module.css
+++ b/packages/dashboard-ui/src/components/EmptyState.module.css
@@ -2,7 +2,7 @@
display: flex;
justify-content: center;
width: 100%;
- padding: 32px 0 48px;
+ padding: 20px 0 28px;
}
.rootDashed {
@@ -10,7 +10,7 @@
flex-direction: column;
align-items: center;
justify-content: center;
- gap: 24px;
+ gap: 12px;
width: 100%;
max-width: 880px;
margin-inline: auto;
@@ -20,7 +20,7 @@
.rootDashedInline {
max-width: 100%;
- padding: 24px 12px;
+ padding: 12px 8px 16px;
}
.messageDashed {
@@ -55,36 +55,52 @@
.addBox {
appearance: none;
+ -webkit-appearance: none;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
- gap: 12px;
+ gap: 6px;
width: 100%;
- max-width: 880px;
- min-height: 180px;
- padding: 32px 28px;
- background: var(--glass-bg);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border: 2px dashed rgba(var(--primary-rgb) / 0.38);
- border-radius: var(--radius);
- color: var(--primary);
+ max-width: 480px;
+ min-height: 76px;
+ padding: 12px 14px;
+ background: none;
+ background-color: transparent;
+ border-width: 2px;
+ border-style: dashed;
+ border-color: #64748b;
+ border-radius: 12px;
+ color: var(--primary, #0a1628);
font-family: var(--font-ui);
text-decoration: none;
cursor: pointer;
transition:
- background 0.2s ease,
- border-color 0.2s ease,
- transform 0.2s ease,
- box-shadow 0.2s ease;
+ background-color 0.15s ease,
+ border-color 0.15s ease,
+ color 0.15s ease,
+ box-shadow 0.15s ease,
+ transform 0.15s ease;
}
-.addBox:hover {
- background: rgba(var(--primary-rgb) / 0.08);
- border-color: rgba(var(--primary-rgb) / 0.58);
- transform: translateY(-3px);
- box-shadow: var(--card-hover-shadow);
+a.addBox,
+button.addBox {
+ background: none;
+ background-color: transparent;
+ border-width: 2px;
+ border-style: dashed;
+ border-color: #64748b;
+}
+
+.addBox:hover,
+a.addBox:hover,
+button.addBox:hover {
+ background-color: rgba(10, 22, 40, 0.07);
+ border-style: dashed;
+ border-color: #0a1628;
+ color: #0a1628;
+ transform: translateY(-2px);
+ box-shadow: 0 8px 20px rgba(15, 23, 42, 0.1);
}
.addBox:focus-visible {
@@ -96,16 +112,14 @@
display: flex;
align-items: center;
justify-content: center;
- width: 52px;
- height: 52px;
- border-radius: 50%;
- background: rgba(var(--primary-rgb) / 0.12);
+ width: 28px;
+ height: 28px;
}
.addLabel {
- font-size: 14px;
+ font-size: 13px;
font-weight: 600;
- line-height: 1.4;
+ line-height: 1.3;
}
.cell {
@@ -127,4 +141,8 @@
.addBox {
max-width: min(100%, 640px);
}
+
+ .addBox {
+ max-width: 480px;
+ }
}
diff --git a/packages/dashboard-ui/src/components/EmptyState.tsx b/packages/dashboard-ui/src/components/EmptyState.tsx
index 40f87f4..92a566c 100644
--- a/packages/dashboard-ui/src/components/EmptyState.tsx
+++ b/packages/dashboard-ui/src/components/EmptyState.tsx
@@ -79,7 +79,7 @@ function EmptyStateContent({
const addContent = (
<>
-
+
{actionLabel ?
{actionLabel} : null}
>
@@ -92,11 +92,11 @@ function EmptyStateContent({
>
{messageContent}
{actionTo ? (
-
+
{addContent}
) : (
-