@burdenoff/microfe-admin 2026.610.1 → 2026.625.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/dist/admin/components/AccessDenied.js +2 -2
  2. package/dist/admin/components/AccessDenied.js.map +1 -1
  3. package/dist/admin/components/ActivityFeed.js +4 -4
  4. package/dist/admin/components/ActivityFeed.js.map +1 -1
  5. package/dist/admin/components/Charts.js +4 -4
  6. package/dist/admin/components/Charts.js.map +1 -1
  7. package/dist/admin/components/ConfirmDialog.js +6 -6
  8. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  9. package/dist/admin/components/DataTable.js +8 -8
  10. package/dist/admin/components/DataTable.js.map +1 -1
  11. package/dist/admin/components/FormSheet.js +7 -7
  12. package/dist/admin/components/FormSheet.js.map +1 -1
  13. package/dist/admin/components/MetricCard.js +43 -40
  14. package/dist/admin/components/MetricCard.js.map +1 -1
  15. package/dist/admin/components/PageLayout.js +28 -22
  16. package/dist/admin/components/PageLayout.js.map +1 -1
  17. package/dist/admin/components/PermissionGuard.js +1 -1
  18. package/dist/admin/components/PermissionGuard.js.map +1 -1
  19. package/dist/admin/components/RowActions.js +2 -2
  20. package/dist/admin/components/RowActions.js.map +1 -1
  21. package/dist/admin/components/TenantMembersPanel.js +15 -15
  22. package/dist/admin/components/TenantMembersPanel.js.map +1 -1
  23. package/dist/admin/components/TenantSsoPanel.js +14 -14
  24. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  25. package/dist/admin/components/Toast.js +9 -9
  26. package/dist/admin/components/Toast.js.map +1 -1
  27. package/dist/admin/components/forms/SubscriptionForm.js +10 -10
  28. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  29. package/dist/admin/components/forms/TenantForm.js +8 -8
  30. package/dist/admin/components/forms/TenantForm.js.map +1 -1
  31. package/dist/admin/components/forms/TicketForm.js +2 -2
  32. package/dist/admin/components/forms/TicketForm.js.map +1 -1
  33. package/dist/admin/components/forms/UserForm.js +8 -8
  34. package/dist/admin/components/forms/UserForm.js.map +1 -1
  35. package/dist/admin/components/ui.js +58 -51
  36. package/dist/admin/components/ui.js.map +1 -1
  37. package/dist/admin/constants/index.js +8 -8
  38. package/dist/admin/constants/index.js.map +1 -1
  39. package/dist/admin/pages/AuditLogsPage.js +10 -8
  40. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  41. package/dist/admin/pages/BillingPage.js +51 -43
  42. package/dist/admin/pages/BillingPage.js.map +1 -1
  43. package/dist/admin/pages/CommunicationsPage.js +5 -2
  44. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  45. package/dist/admin/pages/ContentPagesPage.js +2 -0
  46. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  47. package/dist/admin/pages/DashboardPage.js +7 -4
  48. package/dist/admin/pages/DashboardPage.js.map +1 -1
  49. package/dist/admin/pages/FeatureFlagsPage.js +6 -4
  50. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  51. package/dist/admin/pages/OrganizationsListPage.js +8 -6
  52. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  53. package/dist/admin/pages/ProductsPage.js +10 -7
  54. package/dist/admin/pages/ProductsPage.js.map +1 -1
  55. package/dist/admin/pages/ReferralsPage.js +27 -23
  56. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  57. package/dist/admin/pages/RolesPermissionsPage.js +11 -8
  58. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  59. package/dist/admin/pages/SREPage.js +12 -8
  60. package/dist/admin/pages/SREPage.js.map +1 -1
  61. package/dist/admin/pages/SandboxSettingsPage.js +21 -20
  62. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  63. package/dist/admin/pages/SettingsPage.js +154 -142
  64. package/dist/admin/pages/SettingsPage.js.map +1 -1
  65. package/dist/admin/pages/SyncFailuresPage.js +14 -11
  66. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  67. package/dist/admin/pages/TenantDetailPage.js +22 -22
  68. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  69. package/dist/admin/pages/TenantFormRoutePage.js +2 -2
  70. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  71. package/dist/admin/pages/TenantsListPage.js +7 -5
  72. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  73. package/dist/admin/pages/UsersListPage.js +8 -6
  74. package/dist/admin/pages/UsersListPage.js.map +1 -1
  75. package/dist/admin/pages/WaitlistPage.js +9 -7
  76. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  77. package/dist/admin/pages/WorkflowsPage.js +6 -3
  78. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  79. package/dist/admin/pages/WorkspacesListPage.js +9 -8
  80. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  81. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"TenantSsoPanel.js","names":[],"sources":["../../../src/admin/components/TenantSsoPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { ShieldCheck, Loader2, Trash2, Save, ChevronDown, ChevronUp } from 'lucide-react';\nimport {\n useAdminGetSsoConfigQuery,\n useAdminSaveSsoConfigMutation,\n useAdminDeleteSsoConfigMutation,\n} from '../../generated/global-operations';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\ninterface TenantSsoPanelProps {\n tenantId: string;\n canManage: boolean;\n}\n\ninterface SsoFormData {\n entityId: string;\n ssoUrl: string;\n sloUrl: string;\n certificate: string;\n enabled: boolean;\n}\n\nconst EMPTY_FORM: SsoFormData = {\n entityId: '',\n ssoUrl: '',\n sloUrl: '',\n certificate: '',\n enabled: true,\n};\n\nexport function TenantSsoPanel({ tenantId, canManage }: TenantSsoPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [expanded, setExpanded] = useState(false);\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<SsoFormData>(EMPTY_FORM);\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const { data, loading, error, refetch } = useAdminGetSsoConfigQuery({\n variables: { tenantId },\n fetchPolicy: 'cache-and-network',\n });\n\n const [saveSso, { loading: saving }] = useAdminSaveSsoConfigMutation();\n const [deleteSso, { loading: deleting }] = useAdminDeleteSsoConfigMutation();\n\n const config = data?.getSSOConfig;\n\n useEffect(() => {\n if (config) {\n setForm({\n entityId: config.entityId,\n ssoUrl: config.ssoUrl,\n sloUrl: config.sloUrl ?? '',\n certificate: config.certificate,\n enabled: config.enabled,\n });\n } else {\n setForm(EMPTY_FORM);\n }\n }, [config]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await saveSso({\n variables: {\n tenantId,\n input: {\n entityId: form.entityId,\n ssoUrl: form.ssoUrl,\n sloUrl: form.sloUrl || undefined,\n certificate: form.certificate,\n enabled: form.enabled,\n },\n },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.savedTitle', 'SSO configuration saved'),\n tr(\n 'admin.tenantDetail.sso.savedDescription',\n 'SAML SSO has been configured for this tenant.'\n )\n );\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to save SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n const handleDelete = async () => {\n try {\n await deleteSso({\n variables: { tenantId },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.deletedTitle', 'SSO configuration removed'),\n tr('admin.tenantDetail.sso.deletedDescription', 'SSO has been disabled for this tenant.')\n );\n setConfirmDelete(false);\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to delete SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n return (\n <>\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('admin.tenantDetail.sso.title', 'SSO Configuration')}\n </CardTitle>\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex items-center gap-1 text-sm text-muted-foreground hover:text-foreground transition-colors\"\n aria-expanded={expanded}\n aria-label={\n expanded\n ? tr('admin.tenantDetail.sso.collapse', 'Collapse SSO configuration')\n : tr('admin.tenantDetail.sso.expand', 'Expand SSO configuration')\n }\n >\n {expanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n </div>\n </CardHeader>\n\n {expanded && (\n <CardContent className=\"space-y-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Loader2 className=\"h-6 w-6 animate-spin text-muted-foreground\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-destructive\">{error.message}</p>\n ) : config && !editing ? (\n /* ── Configured state — read-only view ── */\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${\n config.enabled\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {config.enabled\n ? tr('admin.tenantDetail.sso.enabled', 'Enabled')\n : tr('admin.tenantDetail.sso.disabled', 'Disabled')}\n </span>\n </div>\n\n <dl className=\"grid gap-2 text-sm\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.entityId}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.ssoUrl}</dd>\n </div>\n {config.sloUrl && (\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.sloUrl}</dd>\n </div>\n )}\n </dl>\n\n {canManage && (\n <div className=\"flex gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditing(true)}>\n {tr('admin.tenantDetail.sso.editButton', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-destructive hover:text-destructive\"\n onClick={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('admin.tenantDetail.sso.deleteButton', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : (\n /* ── Not configured or editing — show form ── */\n <form onSubmit={handleSave} className=\"space-y-4\">\n {!config && (\n <p className=\"text-sm text-muted-foreground\">\n {tr(\n 'admin.tenantDetail.sso.notConfigured',\n 'No SSO configuration. Fill in the SAML details below to enable enterprise SSO.'\n )}\n </p>\n )}\n\n <Separator />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-entityId-${tenantId}`}>\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-entityId-${tenantId}`}\n value={form.entityId}\n onChange={(e) => setForm((f) => ({ ...f, entityId: e.target.value }))}\n placeholder=\"https://idp.example.com/entity\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-ssoUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <Input\n id={`sso-ssoUrl-${tenantId}`}\n value={form.ssoUrl}\n onChange={(e) => setForm((f) => ({ ...f, ssoUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/sso\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-sloUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n <span className=\"ml-0.5 text-xs text-muted-foreground\">\n {tr('admin.tenantDetail.sso.optional', '(optional)')}\n </span>\n </label>\n <Input\n id={`sso-sloUrl-${tenantId}`}\n value={form.sloUrl}\n onChange={(e) => setForm((f) => ({ ...f, sloUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/slo\"\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-cert-${tenantId}`}>\n {tr('admin.tenantDetail.sso.certificate', 'X.509 Certificate')}\n <span className=\"ml-1 text-destructive\">*</span>\n </label>\n <textarea\n id={`sso-cert-${tenantId}`}\n value={form.certificate}\n onChange={(e) => setForm((f) => ({ ...f, certificate: e.target.value }))}\n placeholder=\"-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----\"\n required\n rows={5}\n className=\"w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-xs text-foreground placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none\"\n />\n </div>\n\n <div className=\"sm:col-span-2 flex items-center gap-3\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={form.enabled}\n aria-label={tr(\n 'admin.tenantDetail.sso.enabledLabel',\n 'Enable SSO for this tenant'\n )}\n onClick={() => setForm((f) => ({ ...f, enabled: !f.enabled }))}\n className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${\n form.enabled ? 'bg-primary' : 'bg-muted'\n }`}\n >\n <span\n className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${\n form.enabled ? 'translate-x-4' : 'translate-x-0'\n }`}\n />\n </button>\n <span className=\"text-sm\">\n {tr('admin.tenantDetail.sso.enabledLabel', 'Enable SSO for this tenant')}\n </span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Save className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n {tr('admin.tenantDetail.sso.saveButton', 'Save SSO Config')}\n </Button>\n {editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(false)}\n >\n {tr('admin.tenantDetail.sso.cancelButton', 'Cancel')}\n </Button>\n )}\n </div>\n </form>\n )}\n </CardContent>\n )}\n </Card>\n\n <ConfirmDialog\n open={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n title={tr('admin.tenantDetail.sso.deleteTitle', 'Remove SSO configuration?')}\n description={tr(\n 'admin.tenantDetail.sso.deleteDescription',\n 'This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO.'\n )}\n confirmLabel={tr('admin.tenantDetail.sso.deleteConfirm', 'Remove SSO')}\n variant=\"destructive\"\n onConfirm={handleDelete}\n loading={deleting}\n />\n </>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TenantSsoPanel.js","names":[],"sources":["../../../src/admin/components/TenantSsoPanel.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\nimport { ShieldCheck, Loader2, Trash2, Save, ChevronDown, ChevronUp } from 'lucide-react';\nimport {\n useAdminGetSsoConfigQuery,\n useAdminSaveSsoConfigMutation,\n useAdminDeleteSsoConfigMutation,\n} from '../../generated/global-operations';\nimport { Card, CardHeader, CardTitle, CardContent, Button, Input, Separator } from './ui';\nimport { useToast } from './Toast';\nimport { ConfirmDialog } from './ConfirmDialog';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\ninterface TenantSsoPanelProps {\n tenantId: string;\n canManage: boolean;\n}\n\ninterface SsoFormData {\n entityId: string;\n ssoUrl: string;\n sloUrl: string;\n certificate: string;\n enabled: boolean;\n}\n\nconst EMPTY_FORM: SsoFormData = {\n entityId: '',\n ssoUrl: '',\n sloUrl: '',\n certificate: '',\n enabled: true,\n};\n\nexport function TenantSsoPanel({ tenantId, canManage }: TenantSsoPanelProps) {\n const { t } = useI18n();\n const tr = (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params);\n const toast = useToast();\n\n const [expanded, setExpanded] = useState(false);\n const [editing, setEditing] = useState(false);\n const [form, setForm] = useState<SsoFormData>(EMPTY_FORM);\n const [confirmDelete, setConfirmDelete] = useState(false);\n\n const { data, loading, error, refetch } = useAdminGetSsoConfigQuery({\n variables: { tenantId },\n fetchPolicy: 'cache-and-network',\n });\n\n const [saveSso, { loading: saving }] = useAdminSaveSsoConfigMutation();\n const [deleteSso, { loading: deleting }] = useAdminDeleteSsoConfigMutation();\n\n const config = data?.getSSOConfig;\n\n useEffect(() => {\n if (config) {\n setForm({\n entityId: config.entityId,\n ssoUrl: config.ssoUrl,\n sloUrl: config.sloUrl ?? '',\n certificate: config.certificate,\n enabled: config.enabled,\n });\n } else {\n setForm(EMPTY_FORM);\n }\n }, [config]);\n\n const handleSave = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await saveSso({\n variables: {\n tenantId,\n input: {\n entityId: form.entityId,\n ssoUrl: form.ssoUrl,\n sloUrl: form.sloUrl || undefined,\n certificate: form.certificate,\n enabled: form.enabled,\n },\n },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.savedTitle', 'SSO configuration saved'),\n tr(\n 'admin.tenantDetail.sso.savedDescription',\n 'SAML SSO has been configured for this tenant.'\n )\n );\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to save SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n const handleDelete = async () => {\n try {\n await deleteSso({\n variables: { tenantId },\n context: { headers: { 'x-workspace-id': '' } },\n });\n toast.success(\n tr('admin.tenantDetail.sso.deletedTitle', 'SSO configuration removed'),\n tr('admin.tenantDetail.sso.deletedDescription', 'SSO has been disabled for this tenant.')\n );\n setConfirmDelete(false);\n setEditing(false);\n void refetch();\n } catch (err) {\n toast.error(\n tr('admin.tenantDetail.sso.errorTitle', 'Failed to delete SSO config'),\n err instanceof Error\n ? err.message\n : tr('admin.tenantDetail.sso.unknownError', 'Unknown error')\n );\n }\n };\n\n return (\n <>\n <Card>\n <CardHeader>\n <div className=\"flex items-center justify-between\">\n <CardTitle className=\"flex items-center gap-2 text-lg\">\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('admin.tenantDetail.sso.title', 'SSO Configuration')}\n </CardTitle>\n <button\n type=\"button\"\n onClick={() => setExpanded((v) => !v)}\n className=\"flex items-center gap-1 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n aria-expanded={expanded}\n aria-label={\n expanded\n ? tr('admin.tenantDetail.sso.collapse', 'Collapse SSO configuration')\n : tr('admin.tenantDetail.sso.expand', 'Expand SSO configuration')\n }\n >\n {expanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n </div>\n </CardHeader>\n\n {expanded && (\n <CardContent className=\"space-y-4\">\n {loading ? (\n <div className=\"flex items-center justify-center py-6\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : error ? (\n <p className=\"text-sm text-status-error-text\">{error.message}</p>\n ) : config && !editing ? (\n /* ── Configured state — read-only view ── */\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <span\n className={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium ${\n config.enabled\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-bg-sunken text-text-secondary'\n }`}\n >\n {config.enabled\n ? tr('admin.tenantDetail.sso.enabled', 'Enabled')\n : tr('admin.tenantDetail.sso.disabled', 'Disabled')}\n </span>\n </div>\n\n <dl className=\"grid gap-2 text-sm\">\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.entityId}</dd>\n </div>\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.ssoUrl}</dd>\n </div>\n {config.sloUrl && (\n <div className=\"flex flex-col gap-0.5\">\n <dt className=\"text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n </dt>\n <dd className=\"font-mono text-xs break-all\">{config.sloUrl}</dd>\n </div>\n )}\n </dl>\n\n {canManage && (\n <div className=\"flex gap-2 pt-2\">\n <Button variant=\"outline\" size=\"sm\" onClick={() => setEditing(true)}>\n {tr('admin.tenantDetail.sso.editButton', 'Edit')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"text-status-error-text hover:text-status-error-text\"\n onClick={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"mr-1.5 h-3.5 w-3.5\" />\n {tr('admin.tenantDetail.sso.deleteButton', 'Remove')}\n </Button>\n </div>\n )}\n </div>\n ) : (\n /* ── Not configured or editing — show form ── */\n <form onSubmit={handleSave} className=\"space-y-4\">\n {!config && (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'admin.tenantDetail.sso.notConfigured',\n 'No SSO configuration. Fill in the SAML details below to enable enterprise SSO.'\n )}\n </p>\n )}\n\n <Separator />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-entityId-${tenantId}`}>\n {tr('admin.tenantDetail.sso.entityId', 'Entity ID')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <Input\n id={`sso-entityId-${tenantId}`}\n value={form.entityId}\n onChange={(e) => setForm((f) => ({ ...f, entityId: e.target.value }))}\n placeholder=\"https://idp.example.com/entity\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-ssoUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.ssoUrl', 'SSO URL')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <Input\n id={`sso-ssoUrl-${tenantId}`}\n value={form.ssoUrl}\n onChange={(e) => setForm((f) => ({ ...f, ssoUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/sso\"\n required\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-sloUrl-${tenantId}`}>\n {tr('admin.tenantDetail.sso.sloUrl', 'SLO URL')}\n <span className=\"ml-0.5 text-xs text-text-secondary\">\n {tr('admin.tenantDetail.sso.optional', '(optional)')}\n </span>\n </label>\n <Input\n id={`sso-sloUrl-${tenantId}`}\n value={form.sloUrl}\n onChange={(e) => setForm((f) => ({ ...f, sloUrl: e.target.value }))}\n placeholder=\"https://idp.example.com/slo\"\n />\n </div>\n\n <div className=\"sm:col-span-2 space-y-1.5\">\n <label className=\"text-sm font-medium\" htmlFor={`sso-cert-${tenantId}`}>\n {tr('admin.tenantDetail.sso.certificate', 'X.509 Certificate')}\n <span className=\"ml-1 text-status-error-text\">*</span>\n </label>\n <textarea\n id={`sso-cert-${tenantId}`}\n value={form.certificate}\n onChange={(e) => setForm((f) => ({ ...f, certificate: e.target.value }))}\n placeholder=\"-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----\"\n required\n rows={5}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 font-mono text-xs text-text-primary placeholder:text-text-secondary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 resize-none\"\n />\n </div>\n\n <div className=\"sm:col-span-2 flex items-center gap-3\">\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={form.enabled}\n aria-label={tr(\n 'admin.tenantDetail.sso.enabledLabel',\n 'Enable SSO for this tenant'\n )}\n onClick={() => setForm((f) => ({ ...f, enabled: !f.enabled }))}\n className={`relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${\n form.enabled ? 'bg-action-primary-bg' : 'bg-bg-sunken'\n }`}\n >\n <span\n className={`pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${\n form.enabled ? 'translate-x-4' : 'translate-x-0'\n }`}\n />\n </button>\n <span className=\"text-sm\">\n {tr('admin.tenantDetail.sso.enabledLabel', 'Enable SSO for this tenant')}\n </span>\n </div>\n </div>\n\n <Separator />\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" size=\"sm\" disabled={saving}>\n {saving ? (\n <Loader2 className=\"mr-1.5 h-3.5 w-3.5 animate-spin\" />\n ) : (\n <Save className=\"mr-1.5 h-3.5 w-3.5\" />\n )}\n {tr('admin.tenantDetail.sso.saveButton', 'Save SSO Config')}\n </Button>\n {editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(false)}\n >\n {tr('admin.tenantDetail.sso.cancelButton', 'Cancel')}\n </Button>\n )}\n </div>\n </form>\n )}\n </CardContent>\n )}\n </Card>\n\n <ConfirmDialog\n open={confirmDelete}\n onClose={() => setConfirmDelete(false)}\n title={tr('admin.tenantDetail.sso.deleteTitle', 'Remove SSO configuration?')}\n description={tr(\n 'admin.tenantDetail.sso.deleteDescription',\n 'This will remove the SAML SSO configuration. Users will no longer be able to sign in via SSO.'\n )}\n confirmLabel={tr('admin.tenantDetail.sso.deleteConfirm', 'Remove SSO')}\n variant=\"destructive\"\n onConfirm={handleDelete}\n loading={deleting}\n />\n </>\n );\n}\n"],"mappings":"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"}
@@ -81,9 +81,9 @@ var _ = {
81
81
  },
82
82
  error: {
83
83
  icon: o,
84
- bg: "bg-destructive/10",
85
- border: "border-destructive/30",
86
- iconColor: "text-destructive"
84
+ bg: "bg-status-error-bg/10",
85
+ border: "border-status-error-border/30",
86
+ iconColor: "text-status-error-text"
87
87
  },
88
88
  warning: {
89
89
  icon: s,
@@ -93,7 +93,7 @@ var _ = {
93
93
  },
94
94
  info: {
95
95
  icon: l,
96
- bg: "bg-primary/10",
96
+ bg: "bg-action-primary-bg/10",
97
97
  border: "border-primary/30",
98
98
  iconColor: "text-primary"
99
99
  }
@@ -104,25 +104,25 @@ function v({ toast: t, onDismiss: n }) {
104
104
  let e = t.duration ?? 4e3, r = setTimeout(n, e);
105
105
  return () => clearTimeout(r);
106
106
  }, [t.duration, n]), /* @__PURE__ */ f("div", {
107
- className: e("flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm", "animate-in slide-in-from-right-full duration-300", "bg-background/95", a, o),
107
+ className: e("flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm", "animate-in slide-in-from-right-full duration-300", "bg-bg-surface/95", a, o),
108
108
  role: "alert",
109
109
  children: [
110
110
  /* @__PURE__ */ d(r, { className: e("h-5 w-5 shrink-0 mt-0.5", s) }),
111
111
  /* @__PURE__ */ f("div", {
112
112
  className: "flex-1 min-w-0",
113
113
  children: [/* @__PURE__ */ d("p", {
114
- className: "text-sm font-medium text-foreground",
114
+ className: "text-sm font-medium text-text-primary",
115
115
  children: t.title
116
116
  }), t.description && /* @__PURE__ */ d("p", {
117
- className: "mt-1 text-xs text-muted-foreground",
117
+ className: "mt-1 text-xs text-text-secondary",
118
118
  children: t.description
119
119
  })]
120
120
  }),
121
121
  /* @__PURE__ */ d("button", {
122
122
  onClick: n,
123
- className: "shrink-0 rounded-md p-1 hover:bg-muted transition-colors",
123
+ className: "shrink-0 rounded-md p-1 hover:bg-bg-sunken transition-colors",
124
124
  "aria-label": "Dismiss",
125
- children: /* @__PURE__ */ d(u, { className: "h-4 w-4 text-muted-foreground" })
125
+ children: /* @__PURE__ */ d(u, { className: "h-4 w-4 text-text-secondary" })
126
126
  })
127
127
  ]
128
128
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Toast.js","names":[],"sources":["../../../src/admin/components/Toast.tsx"],"sourcesContent":["import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';\nimport { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ToastType = 'success' | 'error' | 'warning' | 'info';\n\nexport interface Toast {\n id: string;\n type: ToastType;\n title: string;\n description?: string;\n duration?: number;\n}\n\ninterface ToastContextValue {\n toasts: Toast[];\n addToast: (toast: Omit<Toast, 'id'>) => void;\n removeToast: (id: string) => void;\n success: (title: string, description?: string) => void;\n error: (title: string, description?: string) => void;\n warning: (title: string, description?: string) => void;\n info: (title: string, description?: string) => void;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error('useToast must be used within a ToastProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface ToastProviderProps {\n children: ReactNode;\n}\n\nexport function ToastProvider({ children }: ToastProviderProps) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n\n const addToast = useCallback((toast: Omit<Toast, 'id'>) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n }, []);\n\n const removeToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const success = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'success', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n const error = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'error', title, description, duration: 6000 });\n },\n [addToast]\n );\n\n const warning = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'warning', title, description, duration: 5000 });\n },\n [addToast]\n );\n\n const info = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'info', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n return (\n <ToastContext.Provider value={{ toasts, addToast, removeToast, success, error, warning, info }}>\n {children}\n <ToastContainer toasts={toasts} removeToast={removeToast} />\n </ToastContext.Provider>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\ninterface ToastContainerProps {\n toasts: Toast[];\n removeToast: (id: string) => void;\n}\n\nfunction ToastContainer({ toasts, removeToast }: ToastContainerProps) {\n if (toasts.length === 0) return null;\n\n return (\n <div className=\"fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-sm\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onDismiss={() => removeToast(toast.id)} />\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Toast Item\n// ============================================================================\n\ninterface ToastItemProps {\n toast: Toast;\n onDismiss: () => void;\n}\n\nconst toastStyles: Record<\n ToastType,\n { icon: typeof CheckCircle2; bg: string; border: string; iconColor: string }\n> = {\n success: {\n icon: CheckCircle2,\n bg: 'bg-success/10',\n border: 'border-success/30',\n iconColor: 'text-success',\n },\n error: {\n icon: AlertCircle,\n bg: 'bg-destructive/10',\n border: 'border-destructive/30',\n iconColor: 'text-destructive',\n },\n warning: {\n icon: AlertTriangle,\n bg: 'bg-warning/10',\n border: 'border-warning/30',\n iconColor: 'text-warning',\n },\n info: {\n icon: Info,\n bg: 'bg-primary/10',\n border: 'border-primary/30',\n iconColor: 'text-primary',\n },\n};\n\nfunction ToastItem({ toast, onDismiss }: ToastItemProps) {\n const { icon: Icon, bg, border, iconColor } = toastStyles[toast.type];\n\n // Auto-dismiss\n useEffect(() => {\n const duration = toast.duration ?? 4000;\n const timer = setTimeout(onDismiss, duration);\n return () => clearTimeout(timer);\n }, [toast.duration, onDismiss]);\n\n return (\n <div\n className={cn(\n 'flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm',\n 'animate-in slide-in-from-right-full duration-300',\n 'bg-background/95',\n bg,\n border\n )}\n role=\"alert\"\n >\n <Icon className={cn('h-5 w-5 shrink-0 mt-0.5', iconColor)} />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-foreground\">{toast.title}</p>\n {toast.description && (\n <p className=\"mt-1 text-xs text-muted-foreground\">{toast.description}</p>\n )}\n </div>\n <button\n onClick={onDismiss}\n className=\"shrink-0 rounded-md p-1 hover:bg-muted transition-colors\"\n aria-label=\"Dismiss\"\n >\n <X className=\"h-4 w-4 text-muted-foreground\" />\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;AAgCA,IAAM,IAAe,EAAwC,KAAK;AAElE,SAAgB,IAAW;CACzB,IAAM,IAAU,EAAW,EAAa;AACxC,KAAI,CAAC,EACH,OAAU,MAAM,+CAA+C;AAEjE,QAAO;;AAWT,SAAgB,EAAc,EAAE,eAAgC;CAC9D,IAAM,CAAC,GAAQ,KAAa,EAAkB,EAAE,CAAC,EAE3C,IAAW,GAAa,MAA6B;EACzD,IAAM,IAAK,SAAS,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;AACxE,KAAW,MAAS,CAAC,GAAG,GAAM;GAAE,GAAG;GAAO;GAAI,CAAC,CAAC;IAC/C,EAAE,CAAC,EAEA,IAAc,GAAa,MAAe;AAC9C,KAAW,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAO,EAAG,CAAC;IACnD,EAAE,CAAC,EAEA,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAQ,GACX,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAS;GAAO;GAAa,UAAU;GAAM,CAAC;IAEjE,CAAC,EAAS,CACX,EAEK,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAO,GACV,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAQ;GAAO;GAAa,UAAU;GAAM,CAAC;IAEhE,CAAC,EAAS,CACX;AAED,QACE,kBAAC,EAAa,UAAd;EAAuB,OAAO;GAAE;GAAQ;GAAU;GAAa;GAAS;GAAO;GAAS;GAAM;YAA9F,CACG,GACD,kBAAC,GAAD;GAAwB;GAAqB;GAAe,CAAA,CACtC;;;AAa5B,SAAS,EAAe,EAAE,WAAQ,kBAAoC;AAGpE,QAFI,EAAO,WAAW,IAAU,OAG9B,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAO,KAAK,MACX,kBAAC,GAAD;GAAiC;GAAO,iBAAiB,EAAY,EAAM,GAAG;GAAI,EAAlE,EAAM,GAA4D,CAClF;EACE,CAAA;;AAaV,IAAM,IAGF;CACF,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,OAAO;EACL,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,MAAM;EACJ,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACF;AAED,SAAS,EAAU,EAAE,UAAO,gBAA6B;CACvD,IAAM,EAAE,MAAM,GAAM,OAAI,WAAQ,iBAAc,EAAY,EAAM;AAShE,QANA,QAAgB;EACd,IAAM,IAAW,EAAM,YAAY,KAC7B,IAAQ,WAAW,GAAW,EAAS;AAC7C,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,UAAU,EAAU,CAAC,EAG7B,kBAAC,OAAD;EACE,WAAW,EACT,2EACA,oDACA,oBACA,GACA,EACD;EACD,MAAK;YARP;GAUE,kBAAC,GAAD,EAAM,WAAW,EAAG,2BAA2B,EAAU,EAAI,CAAA;GAC7D,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAuC,EAAM;KAAU,CAAA,EACnE,EAAM,eACL,kBAAC,KAAD;KAAG,WAAU;eAAsC,EAAM;KAAgB,CAAA,CAEvE;;GACN,kBAAC,UAAD;IACE,SAAS;IACT,WAAU;IACV,cAAW;cAEX,kBAAC,GAAD,EAAG,WAAU,iCAAkC,CAAA;IACxC,CAAA;GACL"}
1
+ {"version":3,"file":"Toast.js","names":[],"sources":["../../../src/admin/components/Toast.tsx"],"sourcesContent":["import { createContext, useContext, useState, useCallback, useEffect, type ReactNode } from 'react';\nimport { X, CheckCircle2, AlertCircle, AlertTriangle, Info } from 'lucide-react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type ToastType = 'success' | 'error' | 'warning' | 'info';\n\nexport interface Toast {\n id: string;\n type: ToastType;\n title: string;\n description?: string;\n duration?: number;\n}\n\ninterface ToastContextValue {\n toasts: Toast[];\n addToast: (toast: Omit<Toast, 'id'>) => void;\n removeToast: (id: string) => void;\n success: (title: string, description?: string) => void;\n error: (title: string, description?: string) => void;\n warning: (title: string, description?: string) => void;\n info: (title: string, description?: string) => void;\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst ToastContext = createContext<ToastContextValue | null>(null);\n\nexport function useToast() {\n const context = useContext(ToastContext);\n if (!context) {\n throw new Error('useToast must be used within a ToastProvider');\n }\n return context;\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\ninterface ToastProviderProps {\n children: ReactNode;\n}\n\nexport function ToastProvider({ children }: ToastProviderProps) {\n const [toasts, setToasts] = useState<Toast[]>([]);\n\n const addToast = useCallback((toast: Omit<Toast, 'id'>) => {\n const id = `toast-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n setToasts((prev) => [...prev, { ...toast, id }]);\n }, []);\n\n const removeToast = useCallback((id: string) => {\n setToasts((prev) => prev.filter((t) => t.id !== id));\n }, []);\n\n const success = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'success', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n const error = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'error', title, description, duration: 6000 });\n },\n [addToast]\n );\n\n const warning = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'warning', title, description, duration: 5000 });\n },\n [addToast]\n );\n\n const info = useCallback(\n (title: string, description?: string) => {\n addToast({ type: 'info', title, description, duration: 4000 });\n },\n [addToast]\n );\n\n return (\n <ToastContext.Provider value={{ toasts, addToast, removeToast, success, error, warning, info }}>\n {children}\n <ToastContainer toasts={toasts} removeToast={removeToast} />\n </ToastContext.Provider>\n );\n}\n\n// ============================================================================\n// Toast Container\n// ============================================================================\n\ninterface ToastContainerProps {\n toasts: Toast[];\n removeToast: (id: string) => void;\n}\n\nfunction ToastContainer({ toasts, removeToast }: ToastContainerProps) {\n if (toasts.length === 0) return null;\n\n return (\n <div className=\"fixed bottom-4 right-4 z-[100] flex flex-col gap-2 max-w-sm\">\n {toasts.map((toast) => (\n <ToastItem key={toast.id} toast={toast} onDismiss={() => removeToast(toast.id)} />\n ))}\n </div>\n );\n}\n\n// ============================================================================\n// Toast Item\n// ============================================================================\n\ninterface ToastItemProps {\n toast: Toast;\n onDismiss: () => void;\n}\n\nconst toastStyles: Record<\n ToastType,\n { icon: typeof CheckCircle2; bg: string; border: string; iconColor: string }\n> = {\n success: {\n icon: CheckCircle2,\n bg: 'bg-success/10',\n border: 'border-success/30',\n iconColor: 'text-success',\n },\n error: {\n icon: AlertCircle,\n bg: 'bg-status-error-bg/10',\n border: 'border-status-error-border/30',\n iconColor: 'text-status-error-text',\n },\n warning: {\n icon: AlertTriangle,\n bg: 'bg-warning/10',\n border: 'border-warning/30',\n iconColor: 'text-warning',\n },\n info: {\n icon: Info,\n bg: 'bg-action-primary-bg/10',\n border: 'border-primary/30',\n iconColor: 'text-primary',\n },\n};\n\nfunction ToastItem({ toast, onDismiss }: ToastItemProps) {\n const { icon: Icon, bg, border, iconColor } = toastStyles[toast.type];\n\n // Auto-dismiss\n useEffect(() => {\n const duration = toast.duration ?? 4000;\n const timer = setTimeout(onDismiss, duration);\n return () => clearTimeout(timer);\n }, [toast.duration, onDismiss]);\n\n return (\n <div\n className={cn(\n 'flex items-start gap-3 rounded-lg border p-4 shadow-lg backdrop-blur-sm',\n 'animate-in slide-in-from-right-full duration-300',\n 'bg-bg-surface/95',\n bg,\n border\n )}\n role=\"alert\"\n >\n <Icon className={cn('h-5 w-5 shrink-0 mt-0.5', iconColor)} />\n <div className=\"flex-1 min-w-0\">\n <p className=\"text-sm font-medium text-text-primary\">{toast.title}</p>\n {toast.description && (\n <p className=\"mt-1 text-xs text-text-secondary\">{toast.description}</p>\n )}\n </div>\n <button\n onClick={onDismiss}\n className=\"shrink-0 rounded-md p-1 hover:bg-bg-sunken transition-colors\"\n aria-label=\"Dismiss\"\n >\n <X className=\"h-4 w-4 text-text-secondary\" />\n </button>\n </div>\n );\n}\n"],"mappings":";;;;;AAgCA,IAAM,IAAe,EAAwC,KAAK;AAElE,SAAgB,IAAW;CACzB,IAAM,IAAU,EAAW,EAAa;AACxC,KAAI,CAAC,EACH,OAAU,MAAM,+CAA+C;AAEjE,QAAO;;AAWT,SAAgB,EAAc,EAAE,eAAgC;CAC9D,IAAM,CAAC,GAAQ,KAAa,EAAkB,EAAE,CAAC,EAE3C,IAAW,GAAa,MAA6B;EACzD,IAAM,IAAK,SAAS,KAAK,KAAK,CAAC,GAAG,KAAK,QAAQ,CAAC,SAAS,GAAG,CAAC,MAAM,GAAG,EAAE;AACxE,KAAW,MAAS,CAAC,GAAG,GAAM;GAAE,GAAG;GAAO;GAAI,CAAC,CAAC;IAC/C,EAAE,CAAC,EAEA,IAAc,GAAa,MAAe;AAC9C,KAAW,MAAS,EAAK,QAAQ,MAAM,EAAE,OAAO,EAAG,CAAC;IACnD,EAAE,CAAC,EAEA,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAQ,GACX,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAS;GAAO;GAAa,UAAU;GAAM,CAAC;IAEjE,CAAC,EAAS,CACX,EAEK,IAAU,GACb,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAW;GAAO;GAAa,UAAU;GAAM,CAAC;IAEnE,CAAC,EAAS,CACX,EAEK,IAAO,GACV,GAAe,MAAyB;AACvC,IAAS;GAAE,MAAM;GAAQ;GAAO;GAAa,UAAU;GAAM,CAAC;IAEhE,CAAC,EAAS,CACX;AAED,QACE,kBAAC,EAAa,UAAd;EAAuB,OAAO;GAAE;GAAQ;GAAU;GAAa;GAAS;GAAO;GAAS;GAAM;YAA9F,CACG,GACD,kBAAC,GAAD;GAAwB;GAAqB;GAAe,CAAA,CACtC;;;AAa5B,SAAS,EAAe,EAAE,WAAQ,kBAAoC;AAGpE,QAFI,EAAO,WAAW,IAAU,OAG9B,kBAAC,OAAD;EAAK,WAAU;YACZ,EAAO,KAAK,MACX,kBAAC,GAAD;GAAiC;GAAO,iBAAiB,EAAY,EAAM,GAAG;GAAI,EAAlE,EAAM,GAA4D,CAClF;EACE,CAAA;;AAaV,IAAM,IAGF;CACF,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,OAAO;EACL,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,SAAS;EACP,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACD,MAAM;EACJ,MAAM;EACN,IAAI;EACJ,QAAQ;EACR,WAAW;EACZ;CACF;AAED,SAAS,EAAU,EAAE,UAAO,gBAA6B;CACvD,IAAM,EAAE,MAAM,GAAM,OAAI,WAAQ,iBAAc,EAAY,EAAM;AAShE,QANA,QAAgB;EACd,IAAM,IAAW,EAAM,YAAY,KAC7B,IAAQ,WAAW,GAAW,EAAS;AAC7C,eAAa,aAAa,EAAM;IAC/B,CAAC,EAAM,UAAU,EAAU,CAAC,EAG7B,kBAAC,OAAD;EACE,WAAW,EACT,2EACA,oDACA,oBACA,GACA,EACD;EACD,MAAK;YARP;GAUE,kBAAC,GAAD,EAAM,WAAW,EAAG,2BAA2B,EAAU,EAAI,CAAA;GAC7D,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eAAyC,EAAM;KAAU,CAAA,EACrE,EAAM,eACL,kBAAC,KAAD;KAAG,WAAU;eAAoC,EAAM;KAAgB,CAAA,CAErE;;GACN,kBAAC,UAAD;IACE,SAAS;IACT,WAAU;IACV,cAAW;cAEX,kBAAC,GAAD,EAAG,WAAU,+BAAgC,CAAA;IACtC,CAAA;GACL"}
@@ -12,19 +12,19 @@ function h({ label: e, error: t, required: n, children: r, description: i }) {
12
12
  className: "space-y-1.5",
13
13
  children: [
14
14
  /* @__PURE__ */ m("label", {
15
- className: "text-sm font-medium text-foreground",
15
+ className: "text-sm font-medium text-text-primary",
16
16
  children: [e, n && /* @__PURE__ */ p("span", {
17
- className: "text-destructive ml-0.5",
17
+ className: "text-status-error-text ml-0.5",
18
18
  children: "*"
19
19
  })]
20
20
  }),
21
21
  r,
22
22
  i && !t && /* @__PURE__ */ p("p", {
23
- className: "text-xs text-muted-foreground",
23
+ className: "text-xs text-text-secondary",
24
24
  children: i
25
25
  }),
26
26
  t && /* @__PURE__ */ p("p", {
27
- className: "text-xs text-destructive",
27
+ className: "text-xs text-status-error-text",
28
28
  children: t
29
29
  })
30
30
  ]
@@ -32,7 +32,7 @@ function h({ label: e, error: t, required: n, children: r, description: i }) {
32
32
  }
33
33
  var g = f.forwardRef(({ className: t, options: n, placeholder: r, ...i }, a) => /* @__PURE__ */ m("select", {
34
34
  ref: a,
35
- className: e("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50", t),
35
+ className: e("flex h-9 w-full rounded-md border border-border-default bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]", "disabled:cursor-not-allowed disabled:opacity-50", t),
36
36
  ...i,
37
37
  children: [r && /* @__PURE__ */ p("option", {
38
38
  value: "",
@@ -242,14 +242,14 @@ function x({ open: e, onClose: c, subscription: l, tenantId: u, tenants: d = [],
242
242
  M !== "Custom" && F > 0 && /* @__PURE__ */ p(r, {
243
243
  title: "Cost Summary",
244
244
  children: /* @__PURE__ */ p("div", {
245
- className: "rounded-lg border border-border bg-muted/30 p-4",
245
+ className: "rounded-lg border border-border-subtle bg-bg-sunken/30 p-4",
246
246
  children: /* @__PURE__ */ m("div", {
247
247
  className: "space-y-2",
248
248
  children: [
249
249
  /* @__PURE__ */ m("div", {
250
250
  className: "flex justify-between text-sm",
251
251
  children: [/* @__PURE__ */ p("span", {
252
- className: "text-muted-foreground",
252
+ className: "text-text-secondary",
253
253
  children: "Base Price"
254
254
  }), /* @__PURE__ */ m("span", { children: [
255
255
  "$",
@@ -260,7 +260,7 @@ function x({ open: e, onClose: c, subscription: l, tenantId: u, tenants: d = [],
260
260
  /* @__PURE__ */ m("div", {
261
261
  className: "flex justify-between text-sm",
262
262
  children: [/* @__PURE__ */ p("span", {
263
- className: "text-muted-foreground",
263
+ className: "text-text-secondary",
264
264
  children: "Seats"
265
265
  }), /* @__PURE__ */ m("span", { children: ["× ", P] })]
266
266
  }),
@@ -269,7 +269,7 @@ function x({ open: e, onClose: c, subscription: l, tenantId: u, tenants: d = [],
269
269
  children: [/* @__PURE__ */ p("span", { children: "Annual Discount" }), /* @__PURE__ */ p("span", { children: "-20%" })]
270
270
  }),
271
271
  /* @__PURE__ */ p("div", {
272
- className: "border-t border-border pt-2 mt-2",
272
+ className: "border-t border-border-subtle pt-2 mt-2",
273
273
  children: /* @__PURE__ */ m("div", {
274
274
  className: "flex justify-between font-medium",
275
275
  children: [/* @__PURE__ */ p("span", { children: N === "yearly" ? "Annual Total" : "Monthly Total" }), /* @__PURE__ */ m("span", {
@@ -278,7 +278,7 @@ function x({ open: e, onClose: c, subscription: l, tenantId: u, tenants: d = [],
278
278
  "$",
279
279
  L.toFixed(2),
280
280
  /* @__PURE__ */ m("span", {
281
- className: "text-sm font-normal text-muted-foreground",
281
+ className: "text-sm font-normal text-text-secondary",
282
282
  children: ["/", N === "yearly" ? "year" : "month"]
283
283
  })
284
284
  ]
@@ -1 +1 @@
1
- {"version":3,"file":"SubscriptionForm.js","names":[],"sources":["../../../../src/admin/components/forms/SubscriptionForm.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';\nimport { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport type { Subscription } from '../../types';\n\n/**\n * Form field wrapper with label and error\n */\ninterface FormFieldProps {\n label: string;\n error?: string;\n required?: boolean;\n children: React.ReactNode;\n description?: string;\n}\n\nfunction FormField({ label, error, required, children, description }: FormFieldProps) {\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium text-foreground\">\n {label}\n {required && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {children}\n {description && !error && <p className=\"text-xs text-muted-foreground\">{description}</p>}\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n );\n}\n\n/**\n * Select wrapper styled like Input\n */\ninterface SelectFieldProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nconst SelectField = React.forwardRef<HTMLSelectElement, SelectFieldProps>(\n ({ className, options, placeholder, ...props }, ref) => (\n <select\n ref={ref}\n className={cn(\n 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n )\n);\nSelectField.displayName = 'SelectField';\n\n// Unified form schema (handles both create and edit)\nconst subscriptionFormSchema = z.object({\n tenantId: z.string().min(1, 'Tenant is required'),\n plan: z.enum(['Free', 'Starter', 'Professional', 'Enterprise', 'Custom']),\n billingCycle: z.enum(['monthly', 'yearly']),\n seats: z.coerce.number().int().min(1, 'At least 1 seat is required'),\n status: z.enum(['active', 'cancelled', 'past_due', 'trialing', 'paused']).optional(),\n startDate: z.string().optional(),\n trialEndDate: z.string().optional(),\n});\n\ntype SubscriptionFormData = z.infer<typeof subscriptionFormSchema>;\n\n/**\n * SubscriptionForm props\n */\nexport interface SubscriptionFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing subscription data for editing (undefined for create) */\n subscription?: Subscription;\n /** Pre-selected tenant ID */\n tenantId?: string;\n /** List of available tenants for selection */\n tenants?: { id: string; name: string }[];\n /** Callback on successful save */\n onSave: (data: SubscriptionFormData & { id?: string }) => Promise<void>;\n}\n\nconst planOptions = [\n { value: 'Free', label: 'Free - $0/mo' },\n { value: 'Starter', label: 'Starter - $29/mo' },\n { value: 'Professional', label: 'Professional - $99/mo' },\n { value: 'Enterprise', label: 'Enterprise - $299/mo' },\n { value: 'Custom', label: 'Custom Pricing' },\n];\n\nconst billingCycleOptions = [\n { value: 'monthly', label: 'Monthly' },\n { value: 'yearly', label: 'Yearly (Save 20%)' },\n];\n\nconst statusOptions = [\n { value: 'active', label: 'Active' },\n { value: 'cancelled', label: 'Cancelled' },\n { value: 'past_due', label: 'Past Due' },\n { value: 'trialing', label: 'Trialing' },\n { value: 'paused', label: 'Paused' },\n];\n\n/**\n * SubscriptionForm - Create/Edit subscription form in a slide-over panel\n */\nexport function SubscriptionForm({\n open,\n onClose,\n subscription,\n tenantId,\n tenants = [],\n onSave,\n}: SubscriptionFormProps) {\n const isEditing = !!subscription;\n const formId = 'subscription-form';\n\n const defaultValues: SubscriptionFormData = {\n tenantId: subscription?.tenantId ?? tenantId ?? '',\n plan: (subscription?.plan ?? 'Starter') as SubscriptionFormData['plan'],\n billingCycle: (subscription?.billingCycle ?? 'monthly') as SubscriptionFormData['billingCycle'],\n seats: subscription?.seats ?? 1,\n status: subscription?.status as SubscriptionFormData['status'],\n startDate: subscription?.startDate\n ? new Date(subscription.startDate).toISOString().split('T')[0]\n : new Date().toISOString().split('T')[0],\n trialEndDate: subscription?.trialEndDate\n ? new Date(subscription.trialEndDate).toISOString().split('T')[0]\n : undefined,\n };\n\n const {\n register,\n handleSubmit,\n formState: { errors, isSubmitting },\n reset,\n watch,\n } = useForm<SubscriptionFormData>({\n resolver: zodResolver(subscriptionFormSchema),\n defaultValues,\n });\n\n // Reset form when subscription or tenantId changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, subscription?.id, tenantId]);\n\n const onSubmit = async (data: SubscriptionFormData) => {\n try {\n await onSave({ ...data, id: subscription?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save subscription:', error);\n }\n };\n\n // Calculate estimated cost\n const plan = watch('plan');\n const billingCycle = watch('billingCycle');\n const seats = watch('seats') || 1;\n\n const planPrices: Record<string, number> = {\n Free: 0,\n Starter: 29,\n Professional: 99,\n Enterprise: 299,\n Custom: 0,\n };\n\n const monthlyPrice = planPrices[plan] || 0;\n const yearlyMultiplier = billingCycle === 'yearly' ? 0.8 : 1; // 20% discount for yearly\n const estimatedCost = monthlyPrice * seats * yearlyMultiplier;\n const billingPeriod = billingCycle === 'yearly' ? 12 : 1;\n const totalPerPeriod = estimatedCost * billingPeriod;\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit Subscription' : 'Create Subscription'}\n description={\n isEditing ? 'Modify subscription details' : 'Set up a new subscription for a tenant'\n }\n width=\"md\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Create Subscription'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Tenant Selection */}\n {tenants.length > 0 && !tenantId && (\n <FormSection>\n <FormField\n label=\"Tenant\"\n required\n error={errors.tenantId?.message}\n description=\"Select the tenant for this subscription\"\n >\n <SelectField\n {...register('tenantId')}\n options={tenants.map((t) => ({ value: t.id, label: t.name }))}\n placeholder=\"Select a tenant...\"\n />\n </FormField>\n </FormSection>\n )}\n\n {/* Plan Selection */}\n <FormSection title=\"Plan Details\">\n <FormRow>\n <FormField label=\"Plan\" required>\n <SelectField {...register('plan')} options={planOptions} />\n </FormField>\n\n <FormField label=\"Billing Cycle\" required>\n <SelectField {...register('billingCycle')} options={billingCycleOptions} />\n </FormField>\n </FormRow>\n\n <FormField\n label=\"Seats\"\n required\n error={errors.seats?.message}\n description=\"Number of user seats included\"\n >\n <Input type=\"number\" {...register('seats')} placeholder=\"1\" min={1} max={10000} />\n </FormField>\n\n {isEditing && (\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormSection>\n\n {/* Dates */}\n <FormSection title=\"Dates\">\n <FormRow>\n <FormField label=\"Start Date\" error={errors.startDate?.message}>\n <Input type=\"date\" {...register('startDate')} />\n </FormField>\n\n <FormField\n label=\"Trial End Date\"\n error={errors.trialEndDate?.message}\n description=\"Leave empty for no trial\"\n >\n <Input type=\"date\" {...register('trialEndDate')} />\n </FormField>\n </FormRow>\n </FormSection>\n\n {/* Cost Summary */}\n {plan !== 'Custom' && monthlyPrice > 0 && (\n <FormSection title=\"Cost Summary\">\n <div className=\"rounded-lg border border-border bg-muted/30 p-4\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-muted-foreground\">Base Price</span>\n <span>${monthlyPrice}/user/month</span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-muted-foreground\">Seats</span>\n <span>× {seats}</span>\n </div>\n {billingCycle === 'yearly' && (\n <div className=\"flex justify-between text-sm text-success\">\n <span>Annual Discount</span>\n <span>-20%</span>\n </div>\n )}\n <div className=\"border-t border-border pt-2 mt-2\">\n <div className=\"flex justify-between font-medium\">\n <span>{billingCycle === 'yearly' ? 'Annual Total' : 'Monthly Total'}</span>\n <span className=\"text-lg\">\n ${totalPerPeriod.toFixed(2)}\n <span className=\"text-sm font-normal text-muted-foreground\">\n /{billingCycle === 'yearly' ? 'year' : 'month'}\n </span>\n </span>\n </div>\n </div>\n </div>\n </div>\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"SubscriptionForm.js","names":[],"sources":["../../../../src/admin/components/forms/SubscriptionForm.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';\nimport { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport type { Subscription } from '../../types';\n\n/**\n * Form field wrapper with label and error\n */\ninterface FormFieldProps {\n label: string;\n error?: string;\n required?: boolean;\n children: React.ReactNode;\n description?: string;\n}\n\nfunction FormField({ label, error, required, children, description }: FormFieldProps) {\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium text-text-primary\">\n {label}\n {required && <span className=\"text-status-error-text ml-0.5\">*</span>}\n </label>\n {children}\n {description && !error && <p className=\"text-xs text-text-secondary\">{description}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n}\n\n/**\n * Select wrapper styled like Input\n */\ninterface SelectFieldProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nconst SelectField = React.forwardRef<HTMLSelectElement, SelectFieldProps>(\n ({ className, options, placeholder, ...props }, ref) => (\n <select\n ref={ref}\n className={cn(\n 'flex h-9 w-full rounded-md border border-border-default bg-transparent px-3 py-1 text-sm shadow-sm transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n )\n);\nSelectField.displayName = 'SelectField';\n\n// Unified form schema (handles both create and edit)\nconst subscriptionFormSchema = z.object({\n tenantId: z.string().min(1, 'Tenant is required'),\n plan: z.enum(['Free', 'Starter', 'Professional', 'Enterprise', 'Custom']),\n billingCycle: z.enum(['monthly', 'yearly']),\n seats: z.coerce.number().int().min(1, 'At least 1 seat is required'),\n status: z.enum(['active', 'cancelled', 'past_due', 'trialing', 'paused']).optional(),\n startDate: z.string().optional(),\n trialEndDate: z.string().optional(),\n});\n\ntype SubscriptionFormData = z.infer<typeof subscriptionFormSchema>;\n\n/**\n * SubscriptionForm props\n */\nexport interface SubscriptionFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing subscription data for editing (undefined for create) */\n subscription?: Subscription;\n /** Pre-selected tenant ID */\n tenantId?: string;\n /** List of available tenants for selection */\n tenants?: { id: string; name: string }[];\n /** Callback on successful save */\n onSave: (data: SubscriptionFormData & { id?: string }) => Promise<void>;\n}\n\nconst planOptions = [\n { value: 'Free', label: 'Free - $0/mo' },\n { value: 'Starter', label: 'Starter - $29/mo' },\n { value: 'Professional', label: 'Professional - $99/mo' },\n { value: 'Enterprise', label: 'Enterprise - $299/mo' },\n { value: 'Custom', label: 'Custom Pricing' },\n];\n\nconst billingCycleOptions = [\n { value: 'monthly', label: 'Monthly' },\n { value: 'yearly', label: 'Yearly (Save 20%)' },\n];\n\nconst statusOptions = [\n { value: 'active', label: 'Active' },\n { value: 'cancelled', label: 'Cancelled' },\n { value: 'past_due', label: 'Past Due' },\n { value: 'trialing', label: 'Trialing' },\n { value: 'paused', label: 'Paused' },\n];\n\n/**\n * SubscriptionForm - Create/Edit subscription form in a slide-over panel\n */\nexport function SubscriptionForm({\n open,\n onClose,\n subscription,\n tenantId,\n tenants = [],\n onSave,\n}: SubscriptionFormProps) {\n const isEditing = !!subscription;\n const formId = 'subscription-form';\n\n const defaultValues: SubscriptionFormData = {\n tenantId: subscription?.tenantId ?? tenantId ?? '',\n plan: (subscription?.plan ?? 'Starter') as SubscriptionFormData['plan'],\n billingCycle: (subscription?.billingCycle ?? 'monthly') as SubscriptionFormData['billingCycle'],\n seats: subscription?.seats ?? 1,\n status: subscription?.status as SubscriptionFormData['status'],\n startDate: subscription?.startDate\n ? new Date(subscription.startDate).toISOString().split('T')[0]\n : new Date().toISOString().split('T')[0],\n trialEndDate: subscription?.trialEndDate\n ? new Date(subscription.trialEndDate).toISOString().split('T')[0]\n : undefined,\n };\n\n const {\n register,\n handleSubmit,\n formState: { errors, isSubmitting },\n reset,\n watch,\n } = useForm<SubscriptionFormData>({\n resolver: zodResolver(subscriptionFormSchema),\n defaultValues,\n });\n\n // Reset form when subscription or tenantId changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, subscription?.id, tenantId]);\n\n const onSubmit = async (data: SubscriptionFormData) => {\n try {\n await onSave({ ...data, id: subscription?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save subscription:', error);\n }\n };\n\n // Calculate estimated cost\n const plan = watch('plan');\n const billingCycle = watch('billingCycle');\n const seats = watch('seats') || 1;\n\n const planPrices: Record<string, number> = {\n Free: 0,\n Starter: 29,\n Professional: 99,\n Enterprise: 299,\n Custom: 0,\n };\n\n const monthlyPrice = planPrices[plan] || 0;\n const yearlyMultiplier = billingCycle === 'yearly' ? 0.8 : 1; // 20% discount for yearly\n const estimatedCost = monthlyPrice * seats * yearlyMultiplier;\n const billingPeriod = billingCycle === 'yearly' ? 12 : 1;\n const totalPerPeriod = estimatedCost * billingPeriod;\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit Subscription' : 'Create Subscription'}\n description={\n isEditing ? 'Modify subscription details' : 'Set up a new subscription for a tenant'\n }\n width=\"md\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Create Subscription'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Tenant Selection */}\n {tenants.length > 0 && !tenantId && (\n <FormSection>\n <FormField\n label=\"Tenant\"\n required\n error={errors.tenantId?.message}\n description=\"Select the tenant for this subscription\"\n >\n <SelectField\n {...register('tenantId')}\n options={tenants.map((t) => ({ value: t.id, label: t.name }))}\n placeholder=\"Select a tenant...\"\n />\n </FormField>\n </FormSection>\n )}\n\n {/* Plan Selection */}\n <FormSection title=\"Plan Details\">\n <FormRow>\n <FormField label=\"Plan\" required>\n <SelectField {...register('plan')} options={planOptions} />\n </FormField>\n\n <FormField label=\"Billing Cycle\" required>\n <SelectField {...register('billingCycle')} options={billingCycleOptions} />\n </FormField>\n </FormRow>\n\n <FormField\n label=\"Seats\"\n required\n error={errors.seats?.message}\n description=\"Number of user seats included\"\n >\n <Input type=\"number\" {...register('seats')} placeholder=\"1\" min={1} max={10000} />\n </FormField>\n\n {isEditing && (\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormSection>\n\n {/* Dates */}\n <FormSection title=\"Dates\">\n <FormRow>\n <FormField label=\"Start Date\" error={errors.startDate?.message}>\n <Input type=\"date\" {...register('startDate')} />\n </FormField>\n\n <FormField\n label=\"Trial End Date\"\n error={errors.trialEndDate?.message}\n description=\"Leave empty for no trial\"\n >\n <Input type=\"date\" {...register('trialEndDate')} />\n </FormField>\n </FormRow>\n </FormSection>\n\n {/* Cost Summary */}\n {plan !== 'Custom' && monthlyPrice > 0 && (\n <FormSection title=\"Cost Summary\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-sunken/30 p-4\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Base Price</span>\n <span>${monthlyPrice}/user/month</span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Seats</span>\n <span>× {seats}</span>\n </div>\n {billingCycle === 'yearly' && (\n <div className=\"flex justify-between text-sm text-success\">\n <span>Annual Discount</span>\n <span>-20%</span>\n </div>\n )}\n <div className=\"border-t border-border-subtle pt-2 mt-2\">\n <div className=\"flex justify-between font-medium\">\n <span>{billingCycle === 'yearly' ? 'Annual Total' : 'Monthly Total'}</span>\n <span className=\"text-lg\">\n ${totalPerPeriod.toFixed(2)}\n <span className=\"text-sm font-normal text-text-secondary\">\n /{billingCycle === 'yearly' ? 'year' : 'month'}\n </span>\n </span>\n </div>\n </div>\n </div>\n </div>\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":"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"}
@@ -14,19 +14,19 @@ function y({ label: e, error: t, required: n, children: r, description: i }) {
14
14
  className: "space-y-1.5",
15
15
  children: [
16
16
  /* @__PURE__ */ _("label", {
17
- className: "text-sm font-medium text-foreground",
17
+ className: "text-sm font-medium text-text-primary",
18
18
  children: [e, n && /* @__PURE__ */ g("span", {
19
- className: "text-destructive ml-0.5",
19
+ className: "text-status-error-text ml-0.5",
20
20
  children: "*"
21
21
  })]
22
22
  }),
23
23
  r,
24
24
  i && !t && /* @__PURE__ */ g("p", {
25
- className: "text-xs text-muted-foreground",
25
+ className: "text-xs text-text-secondary",
26
26
  children: i
27
27
  }),
28
28
  t && /* @__PURE__ */ g("p", {
29
- className: "text-xs text-destructive",
29
+ className: "text-xs text-status-error-text",
30
30
  children: t
31
31
  })
32
32
  ]
@@ -34,7 +34,7 @@ function y({ label: e, error: t, required: n, children: r, description: i }) {
34
34
  }
35
35
  var b = h.forwardRef(({ className: t, options: n, placeholder: r, ...i }, a) => /* @__PURE__ */ _("select", {
36
36
  ref: a,
37
- className: e("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50", t),
37
+ className: e("flex h-9 w-full rounded-md border border-border-default bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]", "disabled:cursor-not-allowed disabled:opacity-50", t),
38
38
  ...i,
39
39
  children: [r && /* @__PURE__ */ g("option", {
40
40
  value: "",
@@ -54,14 +54,14 @@ function x({ label: t, checked: n, onChange: r, description: i, disabled: a }) {
54
54
  checked: n,
55
55
  onChange: (e) => r(e.target.checked),
56
56
  disabled: a,
57
- className: e("mt-0.5 h-4 w-4 rounded border border-input shadow-sm", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50", "checked:bg-primary checked:border-primary")
57
+ className: e("mt-0.5 h-4 w-4 rounded border border-border-default shadow-sm", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]", "disabled:cursor-not-allowed disabled:opacity-50", "checked:bg-action-primary-bg checked:border-primary")
58
58
  }), /* @__PURE__ */ _("div", {
59
59
  className: "space-y-0.5",
60
60
  children: [/* @__PURE__ */ g("span", {
61
- className: "text-sm font-medium text-foreground",
61
+ className: "text-sm font-medium text-text-primary",
62
62
  children: t
63
63
  }), i && /* @__PURE__ */ g("p", {
64
- className: "text-xs text-muted-foreground",
64
+ className: "text-xs text-text-secondary",
65
65
  children: i
66
66
  })]
67
67
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"TenantForm.js","names":[],"sources":["../../../../src/admin/components/forms/TenantForm.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useForm, Controller, type ControllerRenderProps } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { Tenant } from '../../types';\n\ninterface FormFieldProps {\n label: string;\n error?: string;\n required?: boolean;\n children: React.ReactNode;\n description?: string;\n}\n\nfunction FormField({ label, error, required, children, description }: FormFieldProps) {\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium text-foreground\">\n {label}\n {required && <span className=\"text-destructive ml-0.5\">*</span>}\n </label>\n {children}\n {description && !error && <p className=\"text-xs text-muted-foreground\">{description}</p>}\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n );\n}\n\ninterface SelectFieldProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nconst SelectField = React.forwardRef<HTMLSelectElement, SelectFieldProps>(\n ({ className, options, placeholder, ...props }, ref) => (\n <select\n ref={ref}\n className={cn(\n 'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n )\n);\nSelectField.displayName = 'SelectField';\n\ninterface CheckboxFieldProps {\n label: string;\n checked: boolean;\n onChange: (checked: boolean) => void;\n description?: string;\n disabled?: boolean;\n}\n\nfunction CheckboxField({ label, checked, onChange, description, disabled }: CheckboxFieldProps) {\n return (\n <label className=\"flex items-start gap-3 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={(e) => onChange(e.target.checked)}\n disabled={disabled}\n className={cn(\n 'mt-0.5 h-4 w-4 rounded border border-input shadow-sm',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'checked:bg-primary checked:border-primary'\n )}\n />\n <div className=\"space-y-0.5\">\n <span className=\"text-sm font-medium text-foreground\">{label}</span>\n {description && <p className=\"text-xs text-muted-foreground\">{description}</p>}\n </div>\n </label>\n );\n}\n\nconst PLAN_VALUES = ['Free', 'Starter', 'Professional', 'Enterprise', 'Custom'] as const;\nconst STATUS_VALUES = ['active', 'suspended', 'pending', 'churned'] as const;\n\nfunction buildTenantFormSchema(tr: (key: string, fallback: string) => string) {\n return z.object({\n name: z\n .string()\n .min(2, tr('admin.tenants.form.errors.nameMin', 'Name must be at least 2 characters'))\n .max(100, tr('admin.tenants.form.errors.nameMax', 'Name must be less than 100 characters')),\n slug: z\n .string()\n .min(2, tr('admin.tenants.form.errors.slugMin', 'Slug must be at least 2 characters'))\n .max(50, tr('admin.tenants.form.errors.slugMax', 'Slug must be less than 50 characters'))\n .regex(\n /^[a-z0-9-]+$/,\n tr(\n 'admin.tenants.form.errors.slugPattern',\n 'Slug can only contain lowercase letters, numbers, and hyphens'\n )\n ),\n domain: z.string().optional(),\n ownerEmail: z\n .string()\n .email(tr('admin.tenants.form.errors.emailInvalid', 'Invalid email address')),\n billingEmail: z\n .string()\n .email(tr('admin.tenants.form.errors.emailInvalid', 'Invalid email address'))\n .optional()\n .or(z.literal('')),\n // plan/status accept any string so that an existing tenant with a legacy\n // or backend-only enum value can be edited without the form silently\n // rewriting it to a known value. The <select> options below restrict\n // user-chosen values; preserving the original keeps \"edit unrelated\n // field, save\" from downgrading the plan/status.\n plan: z.string().min(1),\n status: z.string().optional(),\n settings: z\n .object({\n allowSignup: z.boolean(),\n ssoEnabled: z.boolean(),\n mfaRequired: z.boolean(),\n dataRetentionDays: z.coerce.number().int().min(30).max(3650),\n })\n .optional(),\n });\n}\n\ntype TenantFormData = z.infer<ReturnType<typeof buildTenantFormSchema>>;\n\nexport interface TenantFormProps {\n open: boolean;\n onClose: () => void;\n tenant?: Tenant;\n onSave: (data: TenantFormData & { id?: string }) => Promise<void>;\n}\n\nexport function TenantForm({ open, onClose, tenant, onSave }: TenantFormProps) {\n const isEditing = !!tenant;\n const formId = 'tenant-form';\n const { t } = useI18n();\n const tr = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n\n const tenantFormSchema = React.useMemo(() => buildTenantFormSchema(tr), [tr]);\n\n const planOptions = React.useMemo(() => {\n const base = [\n { value: 'Free', label: tr('admin.tenants.form.plans.free', 'Free') },\n { value: 'Starter', label: tr('admin.tenants.form.plans.starter', 'Starter') },\n {\n value: 'Professional',\n label: tr('admin.tenants.form.plans.professional', 'Professional'),\n },\n { value: 'Enterprise', label: tr('admin.tenants.form.plans.enterprise', 'Enterprise') },\n { value: 'Custom', label: tr('admin.tenants.form.plans.custom', 'Custom') },\n ];\n // If the current tenant carries a plan outside PLAN_VALUES (legacy data\n // or a backend-introduced tier the UI doesn't know yet), surface it as\n // a synthetic option so the user can see what's persisted and either\n // keep it or pick a known plan. Without this the <select> would render\n // blank for an out-of-range value.\n if (tenant?.plan && !(PLAN_VALUES as readonly string[]).includes(tenant.plan)) {\n base.push({\n value: tenant.plan,\n label: tr('admin.tenants.form.plans.legacy', '{{plan}} (legacy)', { plan: tenant.plan }),\n });\n }\n return base;\n }, [tr, tenant?.plan]);\n\n const statusOptions = React.useMemo(() => {\n const base = [\n { value: 'active', label: tr('admin.tenants.form.statuses.active', 'Active') },\n { value: 'suspended', label: tr('admin.tenants.form.statuses.suspended', 'Suspended') },\n { value: 'pending', label: tr('admin.tenants.form.statuses.pending', 'Pending') },\n { value: 'churned', label: tr('admin.tenants.form.statuses.churned', 'Churned') },\n ];\n // Same logic as plan — Tenant['status'] includes 'deleted', 'trial',\n // 'provisioning' (see mappers.ts) which STATUS_VALUES doesn't list.\n // Surface unknown values as a synthetic option so editing an unrelated\n // field doesn't reset status to a wrong value on submit.\n if (tenant?.status && !(STATUS_VALUES as readonly string[]).includes(tenant.status)) {\n base.push({\n value: tenant.status,\n label: tr('admin.tenants.form.statuses.other', '{{status}} (read-only)', {\n status: tenant.status,\n }),\n });\n }\n return base;\n }, [tr, tenant?.status]);\n\n const defaultValues = React.useMemo<TenantFormData>(\n () => ({\n name: tenant?.name ?? '',\n slug: tenant?.slug ?? '',\n domain: tenant?.domain ?? '',\n ownerEmail: tenant?.ownerEmail ?? '',\n billingEmail: tenant?.billingEmail ?? '',\n // Preserve the original plan/status verbatim — even if the backend\n // value is outside the known set. planOptions/statusOptions surface\n // a synthetic \"legacy\" entry so the select can render the current\n // value, and the user can choose to keep it (save preserves) or pick\n // a known value. Coercing to a default here would silently downgrade\n // the tenant on any unrelated edit.\n plan: tenant?.plan ?? 'Free',\n status: tenant?.status,\n settings: {\n allowSignup: tenant?.settings?.allowSignup ?? true,\n ssoEnabled: tenant?.settings?.ssoEnabled ?? false,\n mfaRequired: tenant?.settings?.mfaRequired ?? false,\n dataRetentionDays: tenant?.settings?.dataRetentionDays ?? 365,\n },\n }),\n [tenant]\n );\n\n const {\n register,\n handleSubmit,\n control,\n formState: { errors, isSubmitting },\n reset,\n watch,\n setValue,\n } = useForm<TenantFormData>({\n resolver: zodResolver(tenantFormSchema),\n defaultValues,\n });\n\n // Reset only when the form transitions from closed→open or the entity id\n // changes. Avoids wiping in-progress edits when `cache-and-network` Apollo\n // refetches re-render the parent with a new `tenant` object identity but\n // the user has already typed into the form. Using `defaultValues` in the\n // dep array would re-fire on every refetch and discard those edits.\n const lastResetKey = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!open) {\n lastResetKey.current = null;\n return;\n }\n const key = tenant?.id ?? '__create__';\n if (lastResetKey.current !== key) {\n reset(defaultValues);\n lastResetKey.current = key;\n }\n }, [open, tenant?.id, reset, defaultValues]);\n\n const onSubmit = async (data: TenantFormData) => {\n try {\n await onSave({ ...data, id: tenant?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save tenant:', error);\n }\n };\n\n const name = watch('name');\n const currentSlug = watch('slug');\n React.useEffect(() => {\n if (!isEditing && name && !currentSlug) {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n setValue('slug', slug);\n }\n }, [name, isEditing, setValue, currentSlug]);\n\n const titleEdit = tr('admin.tenants.form.titleEdit', 'Edit Tenant');\n const titleCreate = tr('admin.tenants.form.titleCreate', 'Create Tenant');\n const submitEdit = tr('admin.tenants.form.submitEdit', 'Save Changes');\n const submitCreate = tr('admin.tenants.form.submitCreate', 'Create Tenant');\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? titleEdit : titleCreate}\n description={\n isEditing\n ? tr('admin.tenants.form.descriptionEdit', 'Editing {{name}}', {\n name: tenant?.name ?? '',\n })\n : tr('admin.tenants.form.descriptionCreate', 'Add a new tenant to the platform')\n }\n width=\"lg\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? submitEdit : submitCreate}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n <FormSection title={tr('admin.tenants.form.sections.basic', 'Basic Information')}>\n <FormField\n label={tr('admin.tenants.form.fields.name', 'Tenant Name')}\n required\n error={errors.name?.message}\n >\n <Input\n {...register('name')}\n placeholder={tr('admin.tenants.form.placeholders.name', 'Acme Corporation')}\n aria-invalid={!!errors.name}\n />\n </FormField>\n\n <FormRow>\n <FormField\n label={tr('admin.tenants.form.fields.slug', 'Slug')}\n required\n error={errors.slug?.message}\n description={\n isEditing\n ? tr(\n 'admin.tenants.form.descriptions.slugImmutable',\n 'Slug is set at creation and cannot be changed.'\n )\n : tr('admin.tenants.form.descriptions.slug', 'URL-friendly identifier')\n }\n >\n <Input\n {...register('slug')}\n placeholder={tr('admin.tenants.form.placeholders.slug', 'acme-corp')}\n aria-invalid={!!errors.slug}\n // Slug is part of CreateTenantInput only — UpdateTenantInput\n // has no slug field. Disabling on edit prevents the user from\n // editing a value that would be silently discarded on save.\n disabled={isEditing}\n readOnly={isEditing}\n />\n </FormField>\n\n <FormField\n label={tr('admin.tenants.form.fields.domain', 'Domain')}\n error={errors.domain?.message}\n >\n <Input\n {...register('domain')}\n placeholder={tr('admin.tenants.form.placeholders.domain', 'acme.com')}\n aria-invalid={!!errors.domain}\n />\n </FormField>\n </FormRow>\n </FormSection>\n\n <FormSection title={tr('admin.tenants.form.sections.contact', 'Contact Information')}>\n <FormRow>\n <FormField\n label={tr('admin.tenants.form.fields.ownerEmail', 'Owner Email')}\n required\n error={errors.ownerEmail?.message}\n >\n <Input\n type=\"email\"\n {...register('ownerEmail')}\n placeholder={tr('admin.tenants.form.placeholders.ownerEmail', 'admin@acme.com')}\n aria-invalid={!!errors.ownerEmail}\n />\n </FormField>\n\n <FormField\n label={tr('admin.tenants.form.fields.billingEmail', 'Billing Email')}\n error={errors.billingEmail?.message}\n >\n <Input\n type=\"email\"\n {...register('billingEmail')}\n placeholder={tr('admin.tenants.form.placeholders.billingEmail', 'billing@acme.com')}\n aria-invalid={!!errors.billingEmail}\n />\n </FormField>\n </FormRow>\n </FormSection>\n\n <FormSection title={tr('admin.tenants.form.sections.planStatus', 'Plan & Status')}>\n <FormRow>\n <FormField label={tr('admin.tenants.form.fields.plan', 'Plan')} required>\n <SelectField {...register('plan')} options={planOptions} />\n </FormField>\n\n {isEditing && (\n <FormField label={tr('admin.tenants.form.fields.status', 'Status')}>\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormRow>\n </FormSection>\n\n <FormSection\n title={tr('admin.tenants.form.sections.settings', 'Settings')}\n description={tr(\n 'admin.tenants.form.descriptions.settings',\n 'Configure tenant security and access settings'\n )}\n >\n <div className=\"space-y-3\">\n <Controller\n name=\"settings.allowSignup\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.allowSignup'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.allowSignupLabel', 'Allow Self-Signup')}\n checked={field.value ?? true}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.allowSignupDescription',\n 'Allow users to sign up for this tenant'\n )}\n />\n )}\n />\n\n <Controller\n name=\"settings.ssoEnabled\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.ssoEnabled'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.ssoEnabledLabel', 'Enable SSO')}\n checked={field.value ?? false}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.ssoEnabledDescription',\n 'Enable Single Sign-On authentication'\n )}\n />\n )}\n />\n\n <Controller\n name=\"settings.mfaRequired\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.mfaRequired'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.mfaRequiredLabel', 'Require MFA')}\n checked={field.value ?? false}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.mfaRequiredDescription',\n 'Require multi-factor authentication for all users'\n )}\n />\n )}\n />\n </div>\n\n <FormField\n label={tr('admin.tenants.form.fields.dataRetentionDays', 'Data Retention (days)')}\n error={errors.settings?.dataRetentionDays?.message}\n >\n <Input\n type=\"number\"\n {...register('settings.dataRetentionDays')}\n placeholder=\"365\"\n min={30}\n max={3650}\n />\n </FormField>\n </FormSection>\n </form>\n </FormSheet>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"TenantForm.js","names":[],"sources":["../../../../src/admin/components/forms/TenantForm.tsx"],"sourcesContent":["import * as React from 'react';\nimport { useForm, Controller, type ControllerRenderProps } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport { tWithFallback } from '../../utils/i18n';\nimport type { Tenant } from '../../types';\n\ninterface FormFieldProps {\n label: string;\n error?: string;\n required?: boolean;\n children: React.ReactNode;\n description?: string;\n}\n\nfunction FormField({ label, error, required, children, description }: FormFieldProps) {\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium text-text-primary\">\n {label}\n {required && <span className=\"text-status-error-text ml-0.5\">*</span>}\n </label>\n {children}\n {description && !error && <p className=\"text-xs text-text-secondary\">{description}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n}\n\ninterface SelectFieldProps extends React.SelectHTMLAttributes<HTMLSelectElement> {\n options: { value: string; label: string }[];\n placeholder?: string;\n}\n\nconst SelectField = React.forwardRef<HTMLSelectElement, SelectFieldProps>(\n ({ className, options, placeholder, ...props }, ref) => (\n <select\n ref={ref}\n className={cn(\n 'flex h-9 w-full rounded-md border border-border-default bg-transparent px-3 py-1 text-sm shadow-sm transition-colors',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n {...props}\n >\n {placeholder && (\n <option value=\"\" disabled>\n {placeholder}\n </option>\n )}\n {options.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n </select>\n )\n);\nSelectField.displayName = 'SelectField';\n\ninterface CheckboxFieldProps {\n label: string;\n checked: boolean;\n onChange: (checked: boolean) => void;\n description?: string;\n disabled?: boolean;\n}\n\nfunction CheckboxField({ label, checked, onChange, description, disabled }: CheckboxFieldProps) {\n return (\n <label className=\"flex items-start gap-3 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checked}\n onChange={(e) => onChange(e.target.checked)}\n disabled={disabled}\n className={cn(\n 'mt-0.5 h-4 w-4 rounded border border-border-default shadow-sm',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'checked:bg-action-primary-bg checked:border-primary'\n )}\n />\n <div className=\"space-y-0.5\">\n <span className=\"text-sm font-medium text-text-primary\">{label}</span>\n {description && <p className=\"text-xs text-text-secondary\">{description}</p>}\n </div>\n </label>\n );\n}\n\nconst PLAN_VALUES = ['Free', 'Starter', 'Professional', 'Enterprise', 'Custom'] as const;\nconst STATUS_VALUES = ['active', 'suspended', 'pending', 'churned'] as const;\n\nfunction buildTenantFormSchema(tr: (key: string, fallback: string) => string) {\n return z.object({\n name: z\n .string()\n .min(2, tr('admin.tenants.form.errors.nameMin', 'Name must be at least 2 characters'))\n .max(100, tr('admin.tenants.form.errors.nameMax', 'Name must be less than 100 characters')),\n slug: z\n .string()\n .min(2, tr('admin.tenants.form.errors.slugMin', 'Slug must be at least 2 characters'))\n .max(50, tr('admin.tenants.form.errors.slugMax', 'Slug must be less than 50 characters'))\n .regex(\n /^[a-z0-9-]+$/,\n tr(\n 'admin.tenants.form.errors.slugPattern',\n 'Slug can only contain lowercase letters, numbers, and hyphens'\n )\n ),\n domain: z.string().optional(),\n ownerEmail: z\n .string()\n .email(tr('admin.tenants.form.errors.emailInvalid', 'Invalid email address')),\n billingEmail: z\n .string()\n .email(tr('admin.tenants.form.errors.emailInvalid', 'Invalid email address'))\n .optional()\n .or(z.literal('')),\n // plan/status accept any string so that an existing tenant with a legacy\n // or backend-only enum value can be edited without the form silently\n // rewriting it to a known value. The <select> options below restrict\n // user-chosen values; preserving the original keeps \"edit unrelated\n // field, save\" from downgrading the plan/status.\n plan: z.string().min(1),\n status: z.string().optional(),\n settings: z\n .object({\n allowSignup: z.boolean(),\n ssoEnabled: z.boolean(),\n mfaRequired: z.boolean(),\n dataRetentionDays: z.coerce.number().int().min(30).max(3650),\n })\n .optional(),\n });\n}\n\ntype TenantFormData = z.infer<ReturnType<typeof buildTenantFormSchema>>;\n\nexport interface TenantFormProps {\n open: boolean;\n onClose: () => void;\n tenant?: Tenant;\n onSave: (data: TenantFormData & { id?: string }) => Promise<void>;\n}\n\nexport function TenantForm({ open, onClose, tenant, onSave }: TenantFormProps) {\n const isEditing = !!tenant;\n const formId = 'tenant-form';\n const { t } = useI18n();\n const tr = React.useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string =>\n tWithFallback(t, key, fallback, params),\n [t]\n );\n\n const tenantFormSchema = React.useMemo(() => buildTenantFormSchema(tr), [tr]);\n\n const planOptions = React.useMemo(() => {\n const base = [\n { value: 'Free', label: tr('admin.tenants.form.plans.free', 'Free') },\n { value: 'Starter', label: tr('admin.tenants.form.plans.starter', 'Starter') },\n {\n value: 'Professional',\n label: tr('admin.tenants.form.plans.professional', 'Professional'),\n },\n { value: 'Enterprise', label: tr('admin.tenants.form.plans.enterprise', 'Enterprise') },\n { value: 'Custom', label: tr('admin.tenants.form.plans.custom', 'Custom') },\n ];\n // If the current tenant carries a plan outside PLAN_VALUES (legacy data\n // or a backend-introduced tier the UI doesn't know yet), surface it as\n // a synthetic option so the user can see what's persisted and either\n // keep it or pick a known plan. Without this the <select> would render\n // blank for an out-of-range value.\n if (tenant?.plan && !(PLAN_VALUES as readonly string[]).includes(tenant.plan)) {\n base.push({\n value: tenant.plan,\n label: tr('admin.tenants.form.plans.legacy', '{{plan}} (legacy)', { plan: tenant.plan }),\n });\n }\n return base;\n }, [tr, tenant?.plan]);\n\n const statusOptions = React.useMemo(() => {\n const base = [\n { value: 'active', label: tr('admin.tenants.form.statuses.active', 'Active') },\n { value: 'suspended', label: tr('admin.tenants.form.statuses.suspended', 'Suspended') },\n { value: 'pending', label: tr('admin.tenants.form.statuses.pending', 'Pending') },\n { value: 'churned', label: tr('admin.tenants.form.statuses.churned', 'Churned') },\n ];\n // Same logic as plan — Tenant['status'] includes 'deleted', 'trial',\n // 'provisioning' (see mappers.ts) which STATUS_VALUES doesn't list.\n // Surface unknown values as a synthetic option so editing an unrelated\n // field doesn't reset status to a wrong value on submit.\n if (tenant?.status && !(STATUS_VALUES as readonly string[]).includes(tenant.status)) {\n base.push({\n value: tenant.status,\n label: tr('admin.tenants.form.statuses.other', '{{status}} (read-only)', {\n status: tenant.status,\n }),\n });\n }\n return base;\n }, [tr, tenant?.status]);\n\n const defaultValues = React.useMemo<TenantFormData>(\n () => ({\n name: tenant?.name ?? '',\n slug: tenant?.slug ?? '',\n domain: tenant?.domain ?? '',\n ownerEmail: tenant?.ownerEmail ?? '',\n billingEmail: tenant?.billingEmail ?? '',\n // Preserve the original plan/status verbatim — even if the backend\n // value is outside the known set. planOptions/statusOptions surface\n // a synthetic \"legacy\" entry so the select can render the current\n // value, and the user can choose to keep it (save preserves) or pick\n // a known value. Coercing to a default here would silently downgrade\n // the tenant on any unrelated edit.\n plan: tenant?.plan ?? 'Free',\n status: tenant?.status,\n settings: {\n allowSignup: tenant?.settings?.allowSignup ?? true,\n ssoEnabled: tenant?.settings?.ssoEnabled ?? false,\n mfaRequired: tenant?.settings?.mfaRequired ?? false,\n dataRetentionDays: tenant?.settings?.dataRetentionDays ?? 365,\n },\n }),\n [tenant]\n );\n\n const {\n register,\n handleSubmit,\n control,\n formState: { errors, isSubmitting },\n reset,\n watch,\n setValue,\n } = useForm<TenantFormData>({\n resolver: zodResolver(tenantFormSchema),\n defaultValues,\n });\n\n // Reset only when the form transitions from closed→open or the entity id\n // changes. Avoids wiping in-progress edits when `cache-and-network` Apollo\n // refetches re-render the parent with a new `tenant` object identity but\n // the user has already typed into the form. Using `defaultValues` in the\n // dep array would re-fire on every refetch and discard those edits.\n const lastResetKey = React.useRef<string | null>(null);\n React.useEffect(() => {\n if (!open) {\n lastResetKey.current = null;\n return;\n }\n const key = tenant?.id ?? '__create__';\n if (lastResetKey.current !== key) {\n reset(defaultValues);\n lastResetKey.current = key;\n }\n }, [open, tenant?.id, reset, defaultValues]);\n\n const onSubmit = async (data: TenantFormData) => {\n try {\n await onSave({ ...data, id: tenant?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save tenant:', error);\n }\n };\n\n const name = watch('name');\n const currentSlug = watch('slug');\n React.useEffect(() => {\n if (!isEditing && name && !currentSlug) {\n const slug = name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n setValue('slug', slug);\n }\n }, [name, isEditing, setValue, currentSlug]);\n\n const titleEdit = tr('admin.tenants.form.titleEdit', 'Edit Tenant');\n const titleCreate = tr('admin.tenants.form.titleCreate', 'Create Tenant');\n const submitEdit = tr('admin.tenants.form.submitEdit', 'Save Changes');\n const submitCreate = tr('admin.tenants.form.submitCreate', 'Create Tenant');\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? titleEdit : titleCreate}\n description={\n isEditing\n ? tr('admin.tenants.form.descriptionEdit', 'Editing {{name}}', {\n name: tenant?.name ?? '',\n })\n : tr('admin.tenants.form.descriptionCreate', 'Add a new tenant to the platform')\n }\n width=\"lg\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? submitEdit : submitCreate}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n <FormSection title={tr('admin.tenants.form.sections.basic', 'Basic Information')}>\n <FormField\n label={tr('admin.tenants.form.fields.name', 'Tenant Name')}\n required\n error={errors.name?.message}\n >\n <Input\n {...register('name')}\n placeholder={tr('admin.tenants.form.placeholders.name', 'Acme Corporation')}\n aria-invalid={!!errors.name}\n />\n </FormField>\n\n <FormRow>\n <FormField\n label={tr('admin.tenants.form.fields.slug', 'Slug')}\n required\n error={errors.slug?.message}\n description={\n isEditing\n ? tr(\n 'admin.tenants.form.descriptions.slugImmutable',\n 'Slug is set at creation and cannot be changed.'\n )\n : tr('admin.tenants.form.descriptions.slug', 'URL-friendly identifier')\n }\n >\n <Input\n {...register('slug')}\n placeholder={tr('admin.tenants.form.placeholders.slug', 'acme-corp')}\n aria-invalid={!!errors.slug}\n // Slug is part of CreateTenantInput only — UpdateTenantInput\n // has no slug field. Disabling on edit prevents the user from\n // editing a value that would be silently discarded on save.\n disabled={isEditing}\n readOnly={isEditing}\n />\n </FormField>\n\n <FormField\n label={tr('admin.tenants.form.fields.domain', 'Domain')}\n error={errors.domain?.message}\n >\n <Input\n {...register('domain')}\n placeholder={tr('admin.tenants.form.placeholders.domain', 'acme.com')}\n aria-invalid={!!errors.domain}\n />\n </FormField>\n </FormRow>\n </FormSection>\n\n <FormSection title={tr('admin.tenants.form.sections.contact', 'Contact Information')}>\n <FormRow>\n <FormField\n label={tr('admin.tenants.form.fields.ownerEmail', 'Owner Email')}\n required\n error={errors.ownerEmail?.message}\n >\n <Input\n type=\"email\"\n {...register('ownerEmail')}\n placeholder={tr('admin.tenants.form.placeholders.ownerEmail', 'admin@acme.com')}\n aria-invalid={!!errors.ownerEmail}\n />\n </FormField>\n\n <FormField\n label={tr('admin.tenants.form.fields.billingEmail', 'Billing Email')}\n error={errors.billingEmail?.message}\n >\n <Input\n type=\"email\"\n {...register('billingEmail')}\n placeholder={tr('admin.tenants.form.placeholders.billingEmail', 'billing@acme.com')}\n aria-invalid={!!errors.billingEmail}\n />\n </FormField>\n </FormRow>\n </FormSection>\n\n <FormSection title={tr('admin.tenants.form.sections.planStatus', 'Plan & Status')}>\n <FormRow>\n <FormField label={tr('admin.tenants.form.fields.plan', 'Plan')} required>\n <SelectField {...register('plan')} options={planOptions} />\n </FormField>\n\n {isEditing && (\n <FormField label={tr('admin.tenants.form.fields.status', 'Status')}>\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormRow>\n </FormSection>\n\n <FormSection\n title={tr('admin.tenants.form.sections.settings', 'Settings')}\n description={tr(\n 'admin.tenants.form.descriptions.settings',\n 'Configure tenant security and access settings'\n )}\n >\n <div className=\"space-y-3\">\n <Controller\n name=\"settings.allowSignup\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.allowSignup'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.allowSignupLabel', 'Allow Self-Signup')}\n checked={field.value ?? true}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.allowSignupDescription',\n 'Allow users to sign up for this tenant'\n )}\n />\n )}\n />\n\n <Controller\n name=\"settings.ssoEnabled\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.ssoEnabled'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.ssoEnabledLabel', 'Enable SSO')}\n checked={field.value ?? false}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.ssoEnabledDescription',\n 'Enable Single Sign-On authentication'\n )}\n />\n )}\n />\n\n <Controller\n name=\"settings.mfaRequired\"\n control={control}\n render={({\n field,\n }: {\n field: ControllerRenderProps<TenantFormData, 'settings.mfaRequired'>;\n }) => (\n <CheckboxField\n label={tr('admin.tenants.form.settings.mfaRequiredLabel', 'Require MFA')}\n checked={field.value ?? false}\n onChange={field.onChange}\n description={tr(\n 'admin.tenants.form.settings.mfaRequiredDescription',\n 'Require multi-factor authentication for all users'\n )}\n />\n )}\n />\n </div>\n\n <FormField\n label={tr('admin.tenants.form.fields.dataRetentionDays', 'Data Retention (days)')}\n error={errors.settings?.dataRetentionDays?.message}\n >\n <Input\n type=\"number\"\n {...register('settings.dataRetentionDays')}\n placeholder=\"365\"\n min={30}\n max={3650}\n />\n </FormField>\n </FormSection>\n </form>\n </FormSheet>\n );\n}\n"],"mappings":"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"}
@@ -7,7 +7,7 @@ import { jsx as a, jsxs as o } from "react/jsx-runtime";
7
7
  //#region src/admin/components/forms/TicketForm.tsx
8
8
  var s = i.forwardRef(({ className: t, options: n, placeholder: r, ...i }, s) => /* @__PURE__ */ o("select", {
9
9
  ref: s,
10
- className: e("flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50", t),
10
+ className: e("flex h-9 w-full rounded-md border border-border-default bg-transparent px-3 py-1 text-sm shadow-sm transition-colors", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]", "disabled:cursor-not-allowed disabled:opacity-50", t),
11
11
  ...i,
12
12
  children: [r && /* @__PURE__ */ a("option", {
13
13
  value: "",
@@ -20,7 +20,7 @@ var s = i.forwardRef(({ className: t, options: n, placeholder: r, ...i }, s) =>
20
20
  s.displayName = "SelectField";
21
21
  var c = i.forwardRef(({ className: t, ...n }, r) => /* @__PURE__ */ a("textarea", {
22
22
  ref: r,
23
- className: e("flex min-h-[120px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring", "disabled:cursor-not-allowed disabled:opacity-50", "resize-none", t),
23
+ className: e("flex min-h-[120px] w-full rounded-md border border-border-default bg-transparent px-3 py-2 text-sm shadow-sm transition-colors", "placeholder:text-text-secondary", "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--color-focus-ring)]", "disabled:cursor-not-allowed disabled:opacity-50", "resize-none", t),
24
24
  ...n
25
25
  }));
26
26
  c.displayName = "TextareaField", n({