'use client' /** * DomainsSettingsPage — shared composer for /settings/domains (startsim-o7s). * * Renders the list of EmailDomainClaim rows for the active company, the * add-domain form, and the per-card verify-DNS / verify-email actions. * Admin gating is read off useMembership; only admins see Add + Revoke. */ import * as React from 'react' import { useDomainClaims, useMembershipFromApi } from '@startsimpli/auth' import type { DomainClaimRow } from '@startsimpli/auth' import { DomainClaimCard } from '../DomainClaimCard' import type { DomainClaimLite } from '../types' import { DOMAINS_SETTINGS_PAGE_DEFAULTS, type DomainsSettingsPageClassNames, } from './domains-settings-page-default-class-names' import type { DomainsSettingsApi } from './types' export interface DomainsSettingsPageProps { /** The @startsimpli/api client. */ api: DomainsSettingsApi /** Per-slot className overrides. */ classNames?: DomainsSettingsPageClassNames /** * Optional override for the company id. Defaults to the company resolved * by useMembership() on the current user. */ companyId?: string /** Back link href. Defaults to '/'. */ backHref?: string /** Back link text. Defaults to '← Back'. */ backLabel?: string /** Title. Defaults to 'Email domains'. */ title?: string } /** Loose domain pattern — backend is the canonical validator. */ const DOMAIN_RE = /^[a-z0-9.-]+\.[a-z]{2,}$/i function toClaimLite(c: DomainClaimRow): DomainClaimLite { return { id: c.id, companyId: c.companyId, domain: c.domain, verified: c.verified, verificationMethod: c.verificationMethod, verificationToken: c.verificationToken, verifiedAt: c.verifiedAt, createdAt: c.createdAt, } } export function DomainsSettingsPage({ api, classNames, companyId: companyIdProp, backHref = '/', backLabel = '← Back', title = 'Email domains', }: DomainsSettingsPageProps) { const cls = { ...DOMAINS_SETTINGS_PAGE_DEFAULTS, ...(classNames ?? {}) } const membership = useMembershipFromApi(api) const { company, isAdmin, isLoading: membershipLoading } = membership const companyId = companyIdProp ?? company?.id const claims = useDomainClaims({ fetchClaims: async () => { if (!companyId) return [] const res = await api.domainClaims.list({ companyId }) return res.results.map((c) => ({ id: c.id, companyId: c.companyId, domain: c.domain, verified: c.verified, verificationMethod: c.verificationMethod, verificationToken: c.verificationToken, verifiedAt: c.verifiedAt, createdAt: c.createdAt, })) }, createClaim: async ({ companyId: cid, domain }) => { const c = await api.domainClaims.create({ companyId: cid, domain }) return { id: c.id, companyId: c.companyId, domain: c.domain, verified: c.verified, verificationMethod: c.verificationMethod, verificationToken: c.verificationToken, verifiedAt: c.verifiedAt, createdAt: c.createdAt, } }, verifyDns: async (id) => { const c = await api.domainClaims.verifyDns(id) return { id: c.id, companyId: c.companyId, domain: c.domain, verified: c.verified, verificationMethod: c.verificationMethod, verificationToken: c.verificationToken, verifiedAt: c.verifiedAt, createdAt: c.createdAt, } }, initiateEmailVerification: (id) => api.domainClaims.verifyEmailInitiate(id), submitEmailCode: async (id, code) => { const c = await api.domainClaims.verifyEmailCode(id, code) return { id: c.id, companyId: c.companyId, domain: c.domain, verified: c.verified, verificationMethod: c.verificationMethod, verificationToken: c.verificationToken, verifiedAt: c.verifiedAt, createdAt: c.createdAt, } }, revokeClaim: (id) => api.domainClaims.revoke(id), autoFetch: !!companyId, }) const [showAdd, setShowAdd] = React.useState(false) const [domain, setDomain] = React.useState('') const [addError, setAddError] = React.useState('') const [submitting, setSubmitting] = React.useState(false) async function handleAdd(e: React.FormEvent) { e.preventDefault() setAddError('') const clean = domain.trim().toLowerCase() if (!clean) { setAddError('Domain is required') return } if (!DOMAIN_RE.test(clean)) { setAddError('Enter a valid domain like "example.com"') return } if (!companyId) { setAddError('No company in scope') return } setSubmitting(true) try { await claims.create({ companyId, domain: clean }) setDomain('') setShowAdd(false) } catch (err) { setAddError(err instanceof Error ? err.message : 'Could not add domain') } finally { setSubmitting(false) } } if (membershipLoading) { return
Loading…
} if (!companyId) { return ( ) } return (Verified domains auto-join new signups to your team. Add a domain you own.
Loading domains…
) : claims.error ? (Could not load domains.
No domains claimed yet. Add one to auto-join new signups from your team's email domain.