Add Arvan/Cloudflare DNS choice on domain edit and restyle empty add boxes.

Domain save can upsert tenant records on either provider, overflow menus stay opaque, and empty-state add controls use a dashed outline.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-31 18:46:48 +03:30
co-authored by Cursor
parent 0d62a4ca79
commit c962fe524b
11 changed files with 177 additions and 89 deletions
@@ -27,18 +27,14 @@
.menu {
position: fixed;
z-index: 1200;
isolation: isolate;
min-width: 220px;
padding: 6px;
border-radius: 14px;
border: 1px solid var(--glass-border);
background:
linear-gradient(
180deg,
rgba(255, 255, 255, 0.72) 0%,
var(--glass-bg-strong) 100%
);
backdrop-filter: blur(var(--blur-glass)) saturate(1.85);
-webkit-backdrop-filter: blur(var(--blur-glass)) saturate(1.85);
/* Card glass (0.62) is too thin over row icons; page orbs already use
backdrop-filter, so a second blur on this panel often does nothing. */
background: var(--toast-bg, rgba(255, 255, 255, 0.94));
box-shadow:
0 18px 48px rgba(15, 23, 42, 0.2),
0 0 0 1px rgba(255, 255, 255, 0.45),
@@ -46,6 +42,17 @@
animation: menuIn 0.16s ease;
}
.menu::before {
content: '';
position: absolute;
inset: 0;
z-index: -1;
border-radius: inherit;
pointer-events: none;
backdrop-filter: blur(var(--blur-glass)) saturate(1.85);
-webkit-backdrop-filter: blur(var(--blur-glass)) saturate(1.85);
}
.item {
display: flex;
align-items: center;
@@ -0,0 +1,33 @@
import type { ParkedDnsProvider } from '../types/domain'
import styles from './ParkedDomainsModal.module.css'
interface DnsProviderSwitchProps {
value: ParkedDnsProvider
onChange: (dns: ParkedDnsProvider) => void
disabled?: boolean
}
export function DnsProviderSwitch({ value, onChange, disabled }: DnsProviderSwitchProps) {
return (
<div className={styles.dnsSwitch} role="group" aria-label="DNS provider">
<button
type="button"
className={`${styles.dnsOption} ${value === 'arvan' ? styles.dnsOptionOn : ''}`}
onClick={() => onChange('arvan')}
disabled={disabled}
aria-pressed={value === 'arvan'}
>
Arvan
</button>
<button
type="button"
className={`${styles.dnsOption} ${value === 'cloudflare' ? styles.dnsOptionOn : ''}`}
onClick={() => onChange('cloudflare')}
disabled={disabled}
aria-pressed={value === 'cloudflare'}
>
Cloudflare
</button>
</div>
)
}
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react'
import { Plus, X } from 'lucide-react'
import { Modal } from './Modal'
import { DnsProviderSwitch } from './DnsProviderSwitch'
import { ApiError } from '../lib/api'
import { updateParkedHosts } from '../services/domainService'
import type { DomainListItem, ParkedAlias, ParkedDnsProvider } from '../types/domain'
@@ -135,26 +136,11 @@ export function ParkedDomainsModal({ domain, onClose, onSaved }: ParkedDomainsMo
dir="ltr"
aria-label={`Parked domain ${index + 1}`}
/>
<div className={styles.dnsSwitch} role="group" aria-label="DNS provider">
<button
type="button"
className={`${styles.dnsOption} ${row.dns === 'arvan' ? styles.dnsOptionOn : ''}`}
onClick={() => updateDns(index, 'arvan')}
disabled={submitting}
aria-pressed={row.dns === 'arvan'}
>
Arvan
</button>
<button
type="button"
className={`${styles.dnsOption} ${row.dns === 'cloudflare' ? styles.dnsOptionOn : ''}`}
onClick={() => updateDns(index, 'cloudflare')}
disabled={submitting}
aria-pressed={row.dns === 'cloudflare'}
>
Cloudflare
</button>
</div>
<DnsProviderSwitch
value={row.dns}
onChange={(dns) => updateDns(index, dns)}
disabled={submitting}
/>
<button
type="button"
className={styles.removeRowBtn}
@@ -144,6 +144,14 @@
line-height: 1.3;
}
.dnsUpdateRow {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.alertError {
margin: 0 0 12px;
padding: 12px 14px;
+28 -12
View File
@@ -31,6 +31,7 @@ import type {
PurgeBusinessDataResponse,
PurgeEntityResult,
} from '../types/business'
import type { ParkedDnsProvider } from '../types/domain'
import type { BusinessCategory } from '../types/category'
import type { ListBusinessesParams } from '../services/businessService'
import { ApiError, isAbortError } from '../lib/api'
@@ -58,6 +59,7 @@ import {
} from '../services/aiPromptService'
import type { AiPromptTemplate, DomainAiPrompt } from '../types/aiPrompt'
import { Modal } from '../components/Modal'
import { DnsProviderSwitch } from '../components/DnsProviderSwitch'
import { PrimaryColorSwatchControl } from '../components/PrimaryColorSwatchControl'
import { useToast } from '../context/ToastContext'
import {
@@ -346,6 +348,7 @@ export function BusinessesPage() {
const [domainHost, setDomainHost] = useState('')
const [domainGitRepoUrl, setDomainGitRepoUrl] = useState('')
const [domainUpdateDns, setDomainUpdateDns] = useState(false)
const [domainDnsProvider, setDomainDnsProvider] = useState<ParkedDnsProvider>('arvan')
const [domainSubmitting, setDomainSubmitting] = useState(false)
const [domainError, setDomainError] = useState('')
@@ -933,6 +936,7 @@ export function BusinessesPage() {
setDomainHost(b.domain ?? '')
setDomainGitRepoUrl(b.gitRepoUrl ?? '')
setDomainUpdateDns(!b.domainId)
setDomainDnsProvider('arvan')
setDomainError('')
setDomainOpen(true)
}
@@ -1126,6 +1130,7 @@ export function BusinessesPage() {
setDomainHost('')
setDomainGitRepoUrl('')
setDomainUpdateDns(false)
setDomainDnsProvider('arvan')
setDomainError('')
}
@@ -1144,6 +1149,7 @@ export function BusinessesPage() {
const updated = (await updateBusinessDomain(businessId, domainId, {
host,
updateDns: domainUpdateDns,
...(domainUpdateDns ? { dnsProvider: domainDnsProvider } : {}),
...(gitRepoUrl ? { gitRepoUrl } : {}),
})) as {
id: number
@@ -1193,6 +1199,7 @@ export function BusinessesPage() {
host,
isPrimary: true,
updateDns: domainUpdateDns,
...(domainUpdateDns ? { dnsProvider: domainDnsProvider } : {}),
...(gitRepoUrl ? { gitRepoUrl } : {}),
})) as {
id: number
@@ -1248,6 +1255,7 @@ export function BusinessesPage() {
setDomainHost('')
setDomainGitRepoUrl('')
setDomainUpdateDns(false)
setDomainDnsProvider('arvan')
setDomainError('')
} catch (err) {
if (submitId !== domainSubmitGenRef.current) return
@@ -1998,7 +2006,7 @@ export function BusinessesPage() {
<p className={styles.helperText} role="status">
{domainGitRepoUrl.trim()
? 'Saving domain and setting up storefront on the websites server…'
: 'Saving domain and updating Arvan DNS…'}
: `Saving domain and updating ${domainDnsProvider === 'cloudflare' ? 'Cloudflare' : 'Arvan'} DNS…`}
</p>
) : null}
<div className={styles.field}>
@@ -2029,19 +2037,27 @@ export function BusinessesPage() {
</p>
</div>
<div style={{ height: 12 }} />
<label className={styles.entityCheck} htmlFor="domain-update-dns">
<input
id="domain-update-dns"
type="checkbox"
checked={domainUpdateDns}
onChange={(e) => setDomainUpdateDns(e.target.checked)}
disabled={domainSubmitting}
<div className={styles.dnsUpdateRow}>
<label className={styles.entityCheck} htmlFor="domain-update-dns">
<input
id="domain-update-dns"
type="checkbox"
checked={domainUpdateDns}
onChange={(e) => setDomainUpdateDns(e.target.checked)}
disabled={domainSubmitting}
/>
<span>Update DNS records</span>
</label>
<DnsProviderSwitch
value={domainDnsProvider}
onChange={setDomainDnsProvider}
disabled={domainSubmitting || !domainUpdateDns}
/>
<span>Update DNS records</span>
</label>
</div>
<p className={styles.subText} style={{ marginTop: 6 }}>
When checked, upserts Arvan records: @ ANAME → ns.meshkee.com, plus www,
business, customer, and api CNAMEs.
{domainDnsProvider === '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.'}
</p>
<div style={{ height: 12 }} />
<div className={styles.actionsRow}>
+27 -13
View File
@@ -16,10 +16,11 @@ import {
import { ConfirmDeleteModal } from '../components/ConfirmDeleteModal'
import { DomainAiPromptsModal } from '../components/DomainAiPromptsModal'
import { DomainLinksOverflowMenu } from '../components/DomainLinksOverflowMenu'
import { DnsProviderSwitch } from '../components/DnsProviderSwitch'
import { ParkedDomainsModal } from '../components/ParkedDomainsModal'
import { Modal } from '../components/Modal'
import { ToggleSwitch } from '../components/ToggleSwitch'
import type { DomainListItem, DomainsListResponse } from '../types/domain'
import type { DomainListItem, DomainsListResponse, ParkedDnsProvider } from '../types/domain'
import type { ListDomainsParams } from '../services/domainService'
import { ApiError, isAbortError } from '../lib/api'
import {
@@ -106,6 +107,7 @@ export function WebsitesPage() {
const [editExpiresAt, setEditExpiresAt] = useState('')
const [editGitRepoUrl, setEditGitRepoUrl] = useState('')
const [editUpdateDns, setEditUpdateDns] = useState(false)
const [editDnsProvider, setEditDnsProvider] = useState<ParkedDnsProvider>('arvan')
const [editSubmitting, setEditSubmitting] = useState(false)
const [editError, setEditError] = useState('')
@@ -179,6 +181,7 @@ export function WebsitesPage() {
setEditExpiresAt(domain.expiresAt ? domain.expiresAt.slice(0, 10) : '')
setEditGitRepoUrl(domain.gitRepoUrl ?? '')
setEditUpdateDns(false)
setEditDnsProvider('arvan')
setEditError('')
setEditOpen(true)
}
@@ -192,6 +195,7 @@ export function WebsitesPage() {
const updated = (await updateDomain(editDomain.id, {
host: editHost.trim(),
updateDns: editUpdateDns,
...(editUpdateDns ? { dnsProvider: editDnsProvider } : {}),
...(editExpiresAt ? { expiresAt: new Date(editExpiresAt).toISOString() } : {}),
...(gitRepoUrl ? { gitRepoUrl } : {}),
})) as {
@@ -204,6 +208,7 @@ export function WebsitesPage() {
setEditDomain(null)
setEditGitRepoUrl('')
setEditUpdateDns(false)
setEditDnsProvider('arvan')
if (updated.provisionError) {
showToast(`"${editHost.trim()}" was updated, but storefront setup failed.`, 'error')
showToast(updated.provisionError, 'error')
@@ -806,6 +811,7 @@ export function WebsitesPage() {
setEditDomain(null)
setEditGitRepoUrl('')
setEditUpdateDns(false)
setEditDnsProvider('arvan')
setEditError('')
}}
>
@@ -818,7 +824,7 @@ export function WebsitesPage() {
<p className={tableStyles.helperText} role="status">
{editGitRepoUrl.trim()
? 'Saving domain and setting up storefront on the websites server…'
: 'Saving domain and updating Arvan DNS…'}
: `Saving domain and updating ${editDnsProvider === 'cloudflare' ? 'Cloudflare' : 'Arvan'} DNS…`}
</p>
) : null}
<div className={tableStyles.field}>
@@ -859,19 +865,27 @@ export function WebsitesPage() {
</p>
</div>
<div style={{ height: 12 }} />
<label className={tableStyles.entityCheck} htmlFor="edit-domain-update-dns">
<input
id="edit-domain-update-dns"
type="checkbox"
checked={editUpdateDns}
onChange={(e) => setEditUpdateDns(e.target.checked)}
disabled={editSubmitting}
<div className={tableStyles.dnsUpdateRow}>
<label className={tableStyles.entityCheck} htmlFor="edit-domain-update-dns">
<input
id="edit-domain-update-dns"
type="checkbox"
checked={editUpdateDns}
onChange={(e) => setEditUpdateDns(e.target.checked)}
disabled={editSubmitting}
/>
<span>Update DNS records</span>
</label>
<DnsProviderSwitch
value={editDnsProvider}
onChange={setEditDnsProvider}
disabled={editSubmitting || !editUpdateDns}
/>
<span>Update DNS records</span>
</label>
</div>
<p className={tableStyles.subText} style={{ marginTop: 6 }}>
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.'}
</p>
<div style={{ height: 12 }} />
<div className={tableStyles.actionsRow}>
@@ -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
}
@@ -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
}