/** * TenantEdit Component * * Modal form for creating tenants. Tenants are immutable after creation. * * @component * @layer Presentation */ import { useState } from 'react'; import { Building2, Globe, AlertCircle } from 'lucide-react'; import { Modal } from '@/components/shared'; import { Tenant, TenantStatus } from '@/domain/entities/Tenant'; import { useCreateTenant } from './hooks'; import { domainSchema } from '@archer/api-interface/schemas/customer-api/request'; import { useTranslation } from '@/i18n/TranslationProvider'; interface TenantFormData { name: string; domain: string; } /** Result returned when a new tenant is created */ export interface TenantCreateResult { tenant: Tenant; productionKey?: string; } interface TenantEditProps { /** Always null — create mode only */ tenant: null; onSave: (result?: TenantCreateResult) => void; onCancel: () => void; } function isValidDomain(domain: string): boolean { return domainSchema.safeParse(domain).success; } export function TenantEdit({ onSave, onCancel }: TenantEditProps) { const { t } = useTranslation(); const { mutate: createTenant, isPending: isCreating } = useCreateTenant(); const wpDomain = import.meta.env.VITE_WP_EMBED === 'true' ? (window as any).archerAi?.domain || '' : ''; const [formData, setFormData] = useState({ name: wpDomain || '', domain: wpDomain, }); const [errors, setErrors] = useState>>({}); const [apiError, setApiError] = useState(null); const validate = (): boolean => { const newErrors: Partial> = {}; if (!formData.name.trim()) { newErrors.name = t('tenants.edit.nameRequired'); } if (!formData.domain.trim()) { newErrors.domain = t('tenants.edit.domainRequired'); } else if (!isValidDomain(formData.domain)) { newErrors.domain = t('tenants.edit.invalidDomain'); } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleChange = (field: keyof TenantFormData, value: string) => { setFormData((prev) => ({ ...prev, [field]: value })); if (apiError) setApiError(null); if (errors[field]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[field]; return newErrors; }); } }; const handleSave = async () => { if (!validate()) return; createTenant( { name: formData.name, domain: formData.domain, status: TenantStatus.ACTIVE, }, { onSuccess: (result) => { onSave({ tenant: result.tenant, productionKey: result.productionKey }); }, onError: (error: any) => { const msg = error?.response?.data?.message || error?.message || 'Failed to create tenant'; setApiError(msg); }, } ); }; const handleKeyPress = (e: React.KeyboardEvent) => { if (e.key === 'Enter' && !isCreating) { handleSave(); } else if (e.key === 'Escape') { onCancel(); } }; return ( } onClose={onCancel} footer={ <> } >
{/* Name Field */}
handleChange('name', e.target.value)} className={`w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 transition-colors ${ errors.name ? 'border-red-300 focus:ring-red-500' : 'border-gray-300 focus:ring-primary-500' }`} placeholder={t('tenants.edit.namePlaceholder')} disabled={isCreating} autoFocus /> {errors.name && (

{errors.name}

)}
{/* Domain Field */}
handleChange('domain', e.target.value.toLowerCase())} className={`w-full pl-10 pr-4 py-2 border rounded-lg focus:outline-none focus:ring-2 transition-colors ${ errors.domain ? 'border-red-300 focus:ring-red-500' : 'border-gray-300 focus:ring-primary-500' }`} placeholder={t('tenants.edit.domainPlaceholder')} disabled={isCreating} />
{errors.domain && (

{errors.domain}

)}
{/* API Error */} {apiError && (
{apiError}
)}
); }