@burdenoff/microfe-admin 2026.610.1 → 2026.624.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 (75) 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 +7 -7
  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 +4 -4
  14. package/dist/admin/components/MetricCard.js.map +1 -1
  15. package/dist/admin/components/PageLayout.js +3 -3
  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 +16 -16
  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 +8 -8
  40. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  41. package/dist/admin/pages/CommunicationsPage.js +2 -2
  42. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  43. package/dist/admin/pages/FeatureFlagsPage.js +4 -4
  44. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  45. package/dist/admin/pages/OrganizationsListPage.js +6 -6
  46. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  47. package/dist/admin/pages/ProductsPage.js +7 -7
  48. package/dist/admin/pages/ProductsPage.js.map +1 -1
  49. package/dist/admin/pages/ReferralsPage.js +23 -23
  50. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  51. package/dist/admin/pages/RolesPermissionsPage.js +8 -8
  52. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  53. package/dist/admin/pages/SREPage.js +8 -8
  54. package/dist/admin/pages/SREPage.js.map +1 -1
  55. package/dist/admin/pages/SandboxSettingsPage.js +20 -20
  56. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  57. package/dist/admin/pages/SettingsPage.js +20 -20
  58. package/dist/admin/pages/SettingsPage.js.map +1 -1
  59. package/dist/admin/pages/SyncFailuresPage.js +11 -11
  60. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  61. package/dist/admin/pages/TenantDetailPage.js +22 -22
  62. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  63. package/dist/admin/pages/TenantFormRoutePage.js +2 -2
  64. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  65. package/dist/admin/pages/TenantsListPage.js +5 -5
  66. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  67. package/dist/admin/pages/UsersListPage.js +6 -6
  68. package/dist/admin/pages/UsersListPage.js.map +1 -1
  69. package/dist/admin/pages/WaitlistPage.js +7 -7
  70. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  71. package/dist/admin/pages/WorkflowsPage.js +3 -3
  72. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  73. package/dist/admin/pages/WorkspacesListPage.js +8 -8
  74. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  75. package/package.json +1 -1
@@ -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({
@@ -1 +1 @@
1
- {"version":3,"file":"TicketForm.js","names":[],"sources":["../../../../src/admin/components/forms/TicketForm.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 { SupportTicket } 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 && <option value=\"\">{placeholder}</option>}\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/**\n * Textarea wrapper styled like Input\n */\ntype TextareaFieldProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nconst TextareaField = React.forwardRef<HTMLTextAreaElement, TextareaFieldProps>(\n ({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n className={cn(\n 'flex min-h-[120px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm transition-colors',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n 'resize-none',\n className\n )}\n {...props}\n />\n )\n);\nTextareaField.displayName = 'TextareaField';\n\n// Unified form schema\nconst ticketFormSchema = z.object({\n subject: z\n .string()\n .min(5, 'Subject must be at least 5 characters')\n .max(200, 'Subject must be less than 200 characters'),\n description: z\n .string()\n .min(10, 'Description must be at least 10 characters')\n .max(5000, 'Description must be less than 5000 characters'),\n category: z.enum(['billing', 'technical', 'account', 'feature_request', 'other']),\n priority: z.enum(['low', 'medium', 'high', 'urgent']),\n status: z.enum(['open', 'in_progress', 'waiting', 'resolved', 'closed']).optional(),\n tenantId: z.string().optional(),\n userId: z.string().optional(),\n assigneeId: z.string().optional(),\n});\n\ntype TicketFormData = z.infer<typeof ticketFormSchema>;\n\n/**\n * TicketForm props\n */\nexport interface TicketFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing ticket data for editing (undefined for create) */\n ticket?: SupportTicket;\n /** Pre-selected tenant ID */\n tenantId?: string;\n /** Pre-selected user ID */\n userId?: string;\n /** List of available tenants for selection */\n tenants?: { id: string; name: string }[];\n /** List of available assignees */\n assignees?: { id: string; name: string }[];\n /** Callback on successful save */\n onSave: (data: TicketFormData & { id?: string }) => Promise<void>;\n}\n\nconst categoryOptions = [\n { value: 'billing', label: 'Billing' },\n { value: 'technical', label: 'Technical' },\n { value: 'account', label: 'Account' },\n { value: 'feature_request', label: 'Feature Request' },\n { value: 'other', label: 'Other' },\n];\n\nconst priorityOptions = [\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n { value: 'urgent', label: 'Urgent' },\n];\n\nconst statusOptions = [\n { value: 'open', label: 'Open' },\n { value: 'in_progress', label: 'In Progress' },\n { value: 'waiting', label: 'Waiting on Customer' },\n { value: 'resolved', label: 'Resolved' },\n { value: 'closed', label: 'Closed' },\n];\n\n/**\n * TicketForm - Create/Edit support ticket form in a slide-over panel\n */\nexport function TicketForm({\n open,\n onClose,\n ticket,\n tenantId,\n userId,\n tenants = [],\n assignees = [],\n onSave,\n}: TicketFormProps) {\n const isEditing = !!ticket;\n const formId = 'ticket-form';\n\n const defaultValues: TicketFormData = {\n subject: ticket?.subject ?? '',\n description: ticket?.description ?? '',\n category: (ticket?.category ?? 'other') as TicketFormData['category'],\n priority: (ticket?.priority ?? 'medium') as TicketFormData['priority'],\n status: ticket?.status as TicketFormData['status'],\n tenantId: ticket?.tenantId ?? tenantId ?? '',\n userId: ticket?.userId ?? userId ?? '',\n assigneeId: ticket?.assigneeId ?? '',\n };\n\n const {\n register,\n handleSubmit,\n formState: { errors, isSubmitting },\n reset,\n } = useForm<TicketFormData>({\n resolver: zodResolver(ticketFormSchema),\n defaultValues,\n });\n\n // Reset form when ticket changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, ticket?.id, tenantId, userId]);\n\n const onSubmit = async (data: TicketFormData) => {\n try {\n await onSave({ ...data, id: ticket?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save ticket:', error);\n }\n };\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit Ticket' : 'Create Support Ticket'}\n description={\n isEditing ? `Editing ticket #${ticket.id.slice(0, 8)}` : 'Create a new support ticket'\n }\n width=\"lg\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Create Ticket'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Subject */}\n <FormSection>\n <FormField\n label=\"Subject\"\n required\n error={errors.subject?.message}\n description=\"Brief summary of the issue\"\n >\n <Input\n {...register('subject')}\n placeholder=\"e.g., Unable to access billing page\"\n aria-invalid={!!errors.subject}\n />\n </FormField>\n </FormSection>\n\n {/* Category & Priority */}\n <FormSection title=\"Classification\">\n <FormRow>\n <FormField label=\"Category\" required>\n <SelectField {...register('category')} options={categoryOptions} />\n </FormField>\n\n <FormField label=\"Priority\" required>\n <SelectField {...register('priority')} options={priorityOptions} />\n </FormField>\n </FormRow>\n\n {isEditing && (\n <FormRow>\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n\n {assignees.length > 0 && (\n <FormField label=\"Assignee\">\n <SelectField\n {...register('assigneeId')}\n options={assignees.map((a) => ({ value: a.id, label: a.name }))}\n placeholder=\"Unassigned\"\n />\n </FormField>\n )}\n </FormRow>\n )}\n </FormSection>\n\n {/* Description */}\n <FormSection title=\"Details\">\n <FormField\n label=\"Description\"\n required\n error={errors.description?.message}\n description=\"Provide as much detail as possible\"\n >\n <TextareaField\n {...register('description')}\n placeholder=\"Describe the issue in detail. Include steps to reproduce if applicable...\"\n aria-invalid={!!errors.description}\n rows={6}\n />\n </FormField>\n </FormSection>\n\n {/* Context (Tenant/User) */}\n {tenants.length > 0 && (\n <FormSection title=\"Context\">\n <FormField\n label=\"Related Tenant\"\n description=\"Associate this ticket with a specific tenant\"\n >\n <SelectField\n {...register('tenantId')}\n options={tenants.map((t) => ({ value: t.id, label: t.name }))}\n placeholder=\"Select a tenant (optional)\"\n />\n </FormField>\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":";;;;;;;AA0CA,IAAM,IAAc,EAAM,YACvB,EAAE,cAAW,YAAS,gBAAa,GAAG,KAAS,MAC9C,kBAAC,UAAD;CACO;CACL,WAAW,EACT,+GACA,2EACA,mDACA,EACD;CACD,GAAI;WARN,CAUG,KAAe,kBAAC,UAAD;EAAQ,OAAM;YAAI;EAAqB,CAAA,EACtD,EAAQ,KAAK,MACZ,kBAAC,UAAD;EAAwB,OAAO,EAAI;YAChC,EAAI;EACE,EAFI,EAAI,MAER,CACT,CACK;GAEZ;AACD,EAAY,cAAc;AAO1B,IAAM,IAAgB,EAAM,YACzB,EAAE,cAAW,GAAG,KAAS,MACxB,kBAAC,YAAD;CACO;CACL,WAAW,EACT,yHACA,qCACA,2EACA,mDACA,eACA,EACD;CACD,GAAI;CACJ,CAAA,CAEL;AACD,EAAc,cAAc,iBAGH,EAAS;CAChC,SAAS,GACE,CACR,IAAI,GAAG,wCAAwC,CAC/C,IAAI,KAAK,2CAA2C;CACvD,aAAa,GACF,CACR,IAAI,IAAI,6CAA6C,CACrD,IAAI,KAAM,gDAAgD;CAC7D,UAAU,EAAO;EAAC;EAAW;EAAa;EAAW;EAAmB;EAAQ,CAAC;CACjF,UAAU,EAAO;EAAC;EAAO;EAAU;EAAQ;EAAS,CAAC;CACrD,QAAQ,EAAO;EAAC;EAAQ;EAAe;EAAW;EAAY;EAAS,CAAC,CAAC,UAAU;CACnF,UAAU,GAAU,CAAC,UAAU;CAC/B,QAAQ,GAAU,CAAC,UAAU;CAC7B,YAAY,GAAU,CAAC,UAAU;CAClC,CAAC"}
1
+ {"version":3,"file":"TicketForm.js","names":[],"sources":["../../../../src/admin/components/forms/TicketForm.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 { SupportTicket } 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 && <option value=\"\">{placeholder}</option>}\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/**\n * Textarea wrapper styled like Input\n */\ntype TextareaFieldProps = React.TextareaHTMLAttributes<HTMLTextAreaElement>;\n\nconst TextareaField = React.forwardRef<HTMLTextAreaElement, TextareaFieldProps>(\n ({ className, ...props }, ref) => (\n <textarea\n ref={ref}\n className={cn(\n 'flex min-h-[120px] w-full rounded-md border border-border-default bg-transparent px-3 py-2 text-sm shadow-sm transition-colors',\n 'placeholder:text-text-secondary',\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 'resize-none',\n className\n )}\n {...props}\n />\n )\n);\nTextareaField.displayName = 'TextareaField';\n\n// Unified form schema\nconst ticketFormSchema = z.object({\n subject: z\n .string()\n .min(5, 'Subject must be at least 5 characters')\n .max(200, 'Subject must be less than 200 characters'),\n description: z\n .string()\n .min(10, 'Description must be at least 10 characters')\n .max(5000, 'Description must be less than 5000 characters'),\n category: z.enum(['billing', 'technical', 'account', 'feature_request', 'other']),\n priority: z.enum(['low', 'medium', 'high', 'urgent']),\n status: z.enum(['open', 'in_progress', 'waiting', 'resolved', 'closed']).optional(),\n tenantId: z.string().optional(),\n userId: z.string().optional(),\n assigneeId: z.string().optional(),\n});\n\ntype TicketFormData = z.infer<typeof ticketFormSchema>;\n\n/**\n * TicketForm props\n */\nexport interface TicketFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing ticket data for editing (undefined for create) */\n ticket?: SupportTicket;\n /** Pre-selected tenant ID */\n tenantId?: string;\n /** Pre-selected user ID */\n userId?: string;\n /** List of available tenants for selection */\n tenants?: { id: string; name: string }[];\n /** List of available assignees */\n assignees?: { id: string; name: string }[];\n /** Callback on successful save */\n onSave: (data: TicketFormData & { id?: string }) => Promise<void>;\n}\n\nconst categoryOptions = [\n { value: 'billing', label: 'Billing' },\n { value: 'technical', label: 'Technical' },\n { value: 'account', label: 'Account' },\n { value: 'feature_request', label: 'Feature Request' },\n { value: 'other', label: 'Other' },\n];\n\nconst priorityOptions = [\n { value: 'low', label: 'Low' },\n { value: 'medium', label: 'Medium' },\n { value: 'high', label: 'High' },\n { value: 'urgent', label: 'Urgent' },\n];\n\nconst statusOptions = [\n { value: 'open', label: 'Open' },\n { value: 'in_progress', label: 'In Progress' },\n { value: 'waiting', label: 'Waiting on Customer' },\n { value: 'resolved', label: 'Resolved' },\n { value: 'closed', label: 'Closed' },\n];\n\n/**\n * TicketForm - Create/Edit support ticket form in a slide-over panel\n */\nexport function TicketForm({\n open,\n onClose,\n ticket,\n tenantId,\n userId,\n tenants = [],\n assignees = [],\n onSave,\n}: TicketFormProps) {\n const isEditing = !!ticket;\n const formId = 'ticket-form';\n\n const defaultValues: TicketFormData = {\n subject: ticket?.subject ?? '',\n description: ticket?.description ?? '',\n category: (ticket?.category ?? 'other') as TicketFormData['category'],\n priority: (ticket?.priority ?? 'medium') as TicketFormData['priority'],\n status: ticket?.status as TicketFormData['status'],\n tenantId: ticket?.tenantId ?? tenantId ?? '',\n userId: ticket?.userId ?? userId ?? '',\n assigneeId: ticket?.assigneeId ?? '',\n };\n\n const {\n register,\n handleSubmit,\n formState: { errors, isSubmitting },\n reset,\n } = useForm<TicketFormData>({\n resolver: zodResolver(ticketFormSchema),\n defaultValues,\n });\n\n // Reset form when ticket changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, ticket?.id, tenantId, userId]);\n\n const onSubmit = async (data: TicketFormData) => {\n try {\n await onSave({ ...data, id: ticket?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save ticket:', error);\n }\n };\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit Ticket' : 'Create Support Ticket'}\n description={\n isEditing ? `Editing ticket #${ticket.id.slice(0, 8)}` : 'Create a new support ticket'\n }\n width=\"lg\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Create Ticket'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Subject */}\n <FormSection>\n <FormField\n label=\"Subject\"\n required\n error={errors.subject?.message}\n description=\"Brief summary of the issue\"\n >\n <Input\n {...register('subject')}\n placeholder=\"e.g., Unable to access billing page\"\n aria-invalid={!!errors.subject}\n />\n </FormField>\n </FormSection>\n\n {/* Category & Priority */}\n <FormSection title=\"Classification\">\n <FormRow>\n <FormField label=\"Category\" required>\n <SelectField {...register('category')} options={categoryOptions} />\n </FormField>\n\n <FormField label=\"Priority\" required>\n <SelectField {...register('priority')} options={priorityOptions} />\n </FormField>\n </FormRow>\n\n {isEditing && (\n <FormRow>\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n\n {assignees.length > 0 && (\n <FormField label=\"Assignee\">\n <SelectField\n {...register('assigneeId')}\n options={assignees.map((a) => ({ value: a.id, label: a.name }))}\n placeholder=\"Unassigned\"\n />\n </FormField>\n )}\n </FormRow>\n )}\n </FormSection>\n\n {/* Description */}\n <FormSection title=\"Details\">\n <FormField\n label=\"Description\"\n required\n error={errors.description?.message}\n description=\"Provide as much detail as possible\"\n >\n <TextareaField\n {...register('description')}\n placeholder=\"Describe the issue in detail. Include steps to reproduce if applicable...\"\n aria-invalid={!!errors.description}\n rows={6}\n />\n </FormField>\n </FormSection>\n\n {/* Context (Tenant/User) */}\n {tenants.length > 0 && (\n <FormSection title=\"Context\">\n <FormField\n label=\"Related Tenant\"\n description=\"Associate this ticket with a specific tenant\"\n >\n <SelectField\n {...register('tenantId')}\n options={tenants.map((t) => ({ value: t.id, label: t.name }))}\n placeholder=\"Select a tenant (optional)\"\n />\n </FormField>\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":";;;;;;;AA0CA,IAAM,IAAc,EAAM,YACvB,EAAE,cAAW,YAAS,gBAAa,GAAG,KAAS,MAC9C,kBAAC,UAAD;CACO;CACL,WAAW,EACT,wHACA,gGACA,mDACA,EACD;CACD,GAAI;WARN,CAUG,KAAe,kBAAC,UAAD;EAAQ,OAAM;YAAI;EAAqB,CAAA,EACtD,EAAQ,KAAK,MACZ,kBAAC,UAAD;EAAwB,OAAO,EAAI;YAChC,EAAI;EACE,EAFI,EAAI,MAER,CACT,CACK;GAEZ;AACD,EAAY,cAAc;AAO1B,IAAM,IAAgB,EAAM,YACzB,EAAE,cAAW,GAAG,KAAS,MACxB,kBAAC,YAAD;CACO;CACL,WAAW,EACT,kIACA,mCACA,gGACA,mDACA,eACA,EACD;CACD,GAAI;CACJ,CAAA,CAEL;AACD,EAAc,cAAc,iBAGH,EAAS;CAChC,SAAS,GACE,CACR,IAAI,GAAG,wCAAwC,CAC/C,IAAI,KAAK,2CAA2C;CACvD,aAAa,GACF,CACR,IAAI,IAAI,6CAA6C,CACrD,IAAI,KAAM,gDAAgD;CAC7D,UAAU,EAAO;EAAC;EAAW;EAAa;EAAW;EAAmB;EAAQ,CAAC;CACjF,UAAU,EAAO;EAAC;EAAO;EAAU;EAAQ;EAAS,CAAC;CACrD,QAAQ,EAAO;EAAC;EAAQ;EAAe;EAAW;EAAY;EAAS,CAAC,CAAC,UAAU;CACnF,UAAU,GAAU,CAAC,UAAU;CAC/B,QAAQ,GAAU,CAAC,UAAU;CAC7B,YAAY,GAAU,CAAC,UAAU;CAClC,CAAC"}
@@ -12,19 +12,19 @@ function g({ label: e, error: t, required: n, children: r, description: i }) {
12
12
  className: "space-y-1.5",
13
13
  children: [
14
14
  /* @__PURE__ */ h("label", {
15
- className: "text-sm font-medium text-foreground",
15
+ className: "text-sm font-medium text-text-primary",
16
16
  children: [e, n && /* @__PURE__ */ m("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__ */ m("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__ */ m("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 g({ label: e, error: t, required: n, children: r, description: i }) {
32
32
  }
33
33
  var _ = p.forwardRef(({ className: t, options: n, placeholder: r, ...i }, a) => /* @__PURE__ */ h("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__ */ m("option", {
38
38
  value: "",
@@ -52,14 +52,14 @@ function v({ label: t, checked: n, onChange: r, description: i, disabled: a }) {
52
52
  checked: n,
53
53
  onChange: (e) => r(e.target.checked),
54
54
  disabled: a,
55
- 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")
55
+ 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")
56
56
  }), /* @__PURE__ */ h("div", {
57
57
  className: "space-y-0.5",
58
58
  children: [/* @__PURE__ */ m("span", {
59
- className: "text-sm font-medium text-foreground",
59
+ className: "text-sm font-medium text-text-primary",
60
60
  children: t
61
61
  }), i && /* @__PURE__ */ m("p", {
62
- className: "text-xs text-muted-foreground",
62
+ className: "text-xs text-text-secondary",
63
63
  children: i
64
64
  })]
65
65
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"UserForm.js","names":[],"sources":["../../../../src/admin/components/forms/UserForm.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 { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport type { User } 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/**\n * Checkbox with label\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\n// Unified form schema (handles both create and edit)\nconst userFormSchema = z.object({\n email: z.string().email('Invalid email address'),\n firstName: z\n .string()\n .min(1, 'First name is required')\n .max(50, 'First name must be less than 50 characters'),\n lastName: z\n .string()\n .min(1, 'Last name is required')\n .max(50, 'Last name must be less than 50 characters'),\n role: z.enum(['admin', 'member', 'viewer', 'owner']),\n status: z.enum(['active', 'inactive', 'pending', 'suspended']).optional(),\n tenantId: z.string().optional(),\n sendInvite: z.boolean(),\n});\n\ntype UserFormData = z.infer<typeof userFormSchema>;\n\n/**\n * UserForm props\n */\nexport interface UserFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing user data for editing (undefined for create) */\n user?: User;\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: UserFormData & { id?: string }) => Promise<void>;\n}\n\nconst roleOptions = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'member', label: 'Member' },\n { value: 'admin', label: 'Admin' },\n { value: 'owner', label: 'Owner' },\n];\n\nconst statusOptions = [\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n { value: 'pending', label: 'Pending' },\n { value: 'suspended', label: 'Suspended' },\n];\n\n/**\n * UserForm - Create/Edit user form in a slide-over panel\n */\nexport function UserForm({ open, onClose, user, tenantId, tenants = [], onSave }: UserFormProps) {\n const isEditing = !!user;\n const formId = 'user-form';\n\n // Extract first/last name from user.name if available\n const nameParts =\n user?.firstName && user?.lastName\n ? { firstName: user.firstName, lastName: user.lastName }\n : user?.email\n ? { firstName: '', lastName: '' }\n : { firstName: '', lastName: '' };\n\n const defaultValues: UserFormData = {\n email: user?.email ?? '',\n firstName: nameParts.firstName,\n lastName: nameParts.lastName,\n role: (user?.role?.toLowerCase() ?? 'member') as UserFormData['role'],\n status: user?.status as UserFormData['status'],\n tenantId: user?.tenantId ?? tenantId ?? '',\n sendInvite: !isEditing,\n };\n\n const {\n register,\n handleSubmit,\n control,\n formState: { errors, isSubmitting },\n reset,\n } = useForm<UserFormData>({\n resolver: zodResolver(userFormSchema),\n defaultValues,\n });\n\n // Reset form when user or tenantId changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, user?.id, tenantId]);\n\n const onSubmit = async (data: UserFormData) => {\n try {\n await onSave({ ...data, id: user?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save user:', error);\n }\n };\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit User' : 'Invite User'}\n description={isEditing ? `Editing ${user.email}` : 'Add a new user and send them an invite'}\n width=\"md\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Send Invite'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Basic Information */}\n <FormSection title=\"Basic Information\">\n <FormField label=\"Email\" required error={errors.email?.message}>\n <Input\n type=\"email\"\n {...register('email')}\n placeholder=\"user@example.com\"\n aria-invalid={!!errors.email}\n disabled={isEditing} // Can't change email after creation\n />\n </FormField>\n\n <FormRow>\n <FormField label=\"First Name\" required error={errors.firstName?.message}>\n <Input\n {...register('firstName')}\n placeholder=\"John\"\n aria-invalid={!!errors.firstName}\n />\n </FormField>\n\n <FormField label=\"Last Name\" required error={errors.lastName?.message}>\n <Input {...register('lastName')} placeholder=\"Doe\" aria-invalid={!!errors.lastName} />\n </FormField>\n </FormRow>\n </FormSection>\n\n {/* Role & Access */}\n <FormSection title=\"Role & Access\">\n <FormRow>\n <FormField label=\"Role\" required>\n <SelectField {...register('role')} options={roleOptions} />\n </FormField>\n\n {isEditing && (\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormRow>\n\n {tenants.length > 0 && (\n <FormField label=\"Tenant\" description=\"Assign user to a specific tenant\">\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 )}\n </FormSection>\n\n {/* Invite Options (only for new users) */}\n {!isEditing && (\n <FormSection title=\"Invite Options\">\n <Controller\n name=\"sendInvite\"\n control={control}\n render={({ field }: { field: ControllerRenderProps<UserFormData, 'sendInvite'> }) => (\n <CheckboxField\n label=\"Send Invite Email\"\n checked={field.value ?? true}\n onChange={field.onChange}\n description=\"Send an invitation email to the user with login instructions\"\n />\n )}\n />\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"UserForm.js","names":[],"sources":["../../../../src/admin/components/forms/UserForm.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 { FormSheet, FormSheetFooter, FormSection, FormRow } from '../FormSheet';\nimport { Input } from '../ui';\nimport { cn } from '../../utils/cn';\nimport type { User } 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/**\n * Checkbox with label\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\n// Unified form schema (handles both create and edit)\nconst userFormSchema = z.object({\n email: z.string().email('Invalid email address'),\n firstName: z\n .string()\n .min(1, 'First name is required')\n .max(50, 'First name must be less than 50 characters'),\n lastName: z\n .string()\n .min(1, 'Last name is required')\n .max(50, 'Last name must be less than 50 characters'),\n role: z.enum(['admin', 'member', 'viewer', 'owner']),\n status: z.enum(['active', 'inactive', 'pending', 'suspended']).optional(),\n tenantId: z.string().optional(),\n sendInvite: z.boolean(),\n});\n\ntype UserFormData = z.infer<typeof userFormSchema>;\n\n/**\n * UserForm props\n */\nexport interface UserFormProps {\n /** Whether the form is open */\n open: boolean;\n /** Callback when the form should close */\n onClose: () => void;\n /** Existing user data for editing (undefined for create) */\n user?: User;\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: UserFormData & { id?: string }) => Promise<void>;\n}\n\nconst roleOptions = [\n { value: 'viewer', label: 'Viewer' },\n { value: 'member', label: 'Member' },\n { value: 'admin', label: 'Admin' },\n { value: 'owner', label: 'Owner' },\n];\n\nconst statusOptions = [\n { value: 'active', label: 'Active' },\n { value: 'inactive', label: 'Inactive' },\n { value: 'pending', label: 'Pending' },\n { value: 'suspended', label: 'Suspended' },\n];\n\n/**\n * UserForm - Create/Edit user form in a slide-over panel\n */\nexport function UserForm({ open, onClose, user, tenantId, tenants = [], onSave }: UserFormProps) {\n const isEditing = !!user;\n const formId = 'user-form';\n\n // Extract first/last name from user.name if available\n const nameParts =\n user?.firstName && user?.lastName\n ? { firstName: user.firstName, lastName: user.lastName }\n : user?.email\n ? { firstName: '', lastName: '' }\n : { firstName: '', lastName: '' };\n\n const defaultValues: UserFormData = {\n email: user?.email ?? '',\n firstName: nameParts.firstName,\n lastName: nameParts.lastName,\n role: (user?.role?.toLowerCase() ?? 'member') as UserFormData['role'],\n status: user?.status as UserFormData['status'],\n tenantId: user?.tenantId ?? tenantId ?? '',\n sendInvite: !isEditing,\n };\n\n const {\n register,\n handleSubmit,\n control,\n formState: { errors, isSubmitting },\n reset,\n } = useForm<UserFormData>({\n resolver: zodResolver(userFormSchema),\n defaultValues,\n });\n\n // Reset form when user or tenantId changes\n React.useEffect(() => {\n if (open) {\n reset(defaultValues);\n }\n }, [open, user?.id, tenantId]);\n\n const onSubmit = async (data: UserFormData) => {\n try {\n await onSave({ ...data, id: user?.id });\n onClose();\n } catch (error) {\n console.error('Failed to save user:', error);\n }\n };\n\n return (\n <FormSheet\n open={open}\n onClose={onClose}\n title={isEditing ? 'Edit User' : 'Invite User'}\n description={isEditing ? `Editing ${user.email}` : 'Add a new user and send them an invite'}\n width=\"md\"\n footer={\n <FormSheetFooter\n formId={formId}\n submitLabel={isEditing ? 'Save Changes' : 'Send Invite'}\n onCancel={onClose}\n isSubmitting={isSubmitting}\n />\n }\n >\n <form id={formId} onSubmit={handleSubmit(onSubmit)} className=\"space-y-6\">\n {/* Basic Information */}\n <FormSection title=\"Basic Information\">\n <FormField label=\"Email\" required error={errors.email?.message}>\n <Input\n type=\"email\"\n {...register('email')}\n placeholder=\"user@example.com\"\n aria-invalid={!!errors.email}\n disabled={isEditing} // Can't change email after creation\n />\n </FormField>\n\n <FormRow>\n <FormField label=\"First Name\" required error={errors.firstName?.message}>\n <Input\n {...register('firstName')}\n placeholder=\"John\"\n aria-invalid={!!errors.firstName}\n />\n </FormField>\n\n <FormField label=\"Last Name\" required error={errors.lastName?.message}>\n <Input {...register('lastName')} placeholder=\"Doe\" aria-invalid={!!errors.lastName} />\n </FormField>\n </FormRow>\n </FormSection>\n\n {/* Role & Access */}\n <FormSection title=\"Role & Access\">\n <FormRow>\n <FormField label=\"Role\" required>\n <SelectField {...register('role')} options={roleOptions} />\n </FormField>\n\n {isEditing && (\n <FormField label=\"Status\">\n <SelectField {...register('status')} options={statusOptions} />\n </FormField>\n )}\n </FormRow>\n\n {tenants.length > 0 && (\n <FormField label=\"Tenant\" description=\"Assign user to a specific tenant\">\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 )}\n </FormSection>\n\n {/* Invite Options (only for new users) */}\n {!isEditing && (\n <FormSection title=\"Invite Options\">\n <Controller\n name=\"sendInvite\"\n control={control}\n render={({ field }: { field: ControllerRenderProps<UserFormData, 'sendInvite'> }) => (\n <CheckboxField\n label=\"Send Invite Email\"\n checked={field.value ?? true}\n onChange={field.onChange}\n description=\"Send an invitation email to the user with login instructions\"\n />\n )}\n />\n </FormSection>\n )}\n </form>\n </FormSheet>\n );\n}\n"],"mappings":";;;;;;;;;AAoBA,SAAS,EAAU,EAAE,UAAO,UAAO,aAAU,aAAU,kBAA+B;AACpF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,SAAD;IAAO,WAAU;cAAjB,CACG,GACA,KAAY,kBAAC,QAAD;KAAM,WAAU;eAAgC;KAAQ,CAAA,CAC/D;;GACP;GACA,KAAe,CAAC,KAAS,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAgB,CAAA;GACrF,KAAS,kBAAC,KAAD;IAAG,WAAU;cAAkC;IAAU,CAAA;GAC/D;;;AAYV,IAAM,IAAc,EAAM,YACvB,EAAE,cAAW,YAAS,gBAAa,GAAG,KAAS,MAC9C,kBAAC,UAAD;CACO;CACL,WAAW,EACT,wHACA,gGACA,mDACA,EACD;CACD,GAAI;WARN,CAUG,KACC,kBAAC,UAAD;EAAQ,OAAM;EAAG,UAAA;YACd;EACM,CAAA,EAEV,EAAQ,KAAK,MACZ,kBAAC,UAAD;EAAwB,OAAO,EAAI;YAChC,EAAI;EACE,EAFI,EAAI,MAER,CACT,CACK;GAEZ;AACD,EAAY,cAAc;AAa1B,SAAS,EAAc,EAAE,UAAO,YAAS,aAAU,gBAAa,eAAgC;AAC9F,QACE,kBAAC,SAAD;EAAO,WAAU;YAAjB,CACE,kBAAC,SAAD;GACE,MAAK;GACI;GACT,WAAW,MAAM,EAAS,EAAE,OAAO,QAAQ;GACjC;GACV,WAAW,EACT,iEACA,gGACA,mDACA,sDACD;GACD,CAAA,EACF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAAyC;IAAa,CAAA,EACrE,KAAe,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAgB,CAAA,CACxE;KACA;;;AAKZ,IAAM,IAAiB,EAAS;CAC9B,OAAO,GAAU,CAAC,MAAM,wBAAwB;CAChD,WAAW,GACA,CACR,IAAI,GAAG,yBAAyB,CAChC,IAAI,IAAI,6CAA6C;CACxD,UAAU,GACC,CACR,IAAI,GAAG,wBAAwB,CAC/B,IAAI,IAAI,4CAA4C;CACvD,MAAM,EAAO;EAAC;EAAS;EAAU;EAAU;EAAQ,CAAC;CACpD,QAAQ,EAAO;EAAC;EAAU;EAAY;EAAW;EAAY,CAAC,CAAC,UAAU;CACzE,UAAU,GAAU,CAAC,UAAU;CAC/B,YAAY,GAAW;CACxB,CAAC,EAsBI,IAAc;CAClB;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAS,OAAO;EAAS;CAClC;EAAE,OAAO;EAAS,OAAO;EAAS;CACnC,EAEK,IAAgB;CACpB;EAAE,OAAO;EAAU,OAAO;EAAU;CACpC;EAAE,OAAO;EAAY,OAAO;EAAY;CACxC;EAAE,OAAO;EAAW,OAAO;EAAW;CACtC;EAAE,OAAO;EAAa,OAAO;EAAa;CAC3C;AAKD,SAAgB,EAAS,EAAE,SAAM,YAAS,SAAM,aAAU,aAAU,EAAE,EAAE,aAAyB;CAC/F,IAAM,IAAY,CAAC,CAAC,GACd,IAAS,aAGT,IACJ,GAAM,aAAa,GAAM,WACrB;EAAE,WAAW,EAAK;EAAW,UAAU,EAAK;EAAU,IACtD,GAAM,OACJ;EAAE,WAAW;EAAI,UAAU;EAAI,GAGjC,IAA8B;EAClC,OAAO,GAAM,SAAS;EACtB,WAAW,EAAU;EACrB,UAAU,EAAU;EACpB,MAAO,GAAM,MAAM,aAAa,IAAI;EACpC,QAAQ,GAAM;EACd,UAAU,GAAM,YAAY,KAAY;EACxC,YAAY,CAAC;EACd,EAEK,EACJ,aACA,iBACA,YACA,WAAW,EAAE,WAAQ,mBACrB,aACE,EAAsB;EACxB,UAAU,EAAY,EAAe;EACrC;EACD,CAAC;AAkBF,QAfA,EAAM,gBAAgB;AACpB,EAAI,KACF,EAAM,EAAc;IAErB;EAAC;EAAM,GAAM;EAAI;EAAS,CAAC,EAY5B,kBAAC,GAAD;EACQ;EACG;EACT,OAAO,IAAY,cAAc;EACjC,aAAa,IAAY,WAAW,EAAK,UAAU;EACnD,OAAM;EACN,QACE,kBAAC,GAAD;GACU;GACR,aAAa,IAAY,iBAAiB;GAC1C,UAAU;GACI;GACd,CAAA;YAGJ,kBAAC,QAAD;GAAM,IAAI;GAAQ,UAAU,EAzBf,OAAO,MAAuB;AAC7C,QAAI;AAEF,KADA,MAAM,EAAO;MAAE,GAAG;MAAM,IAAI,GAAM;MAAI,CAAC,EACvC,GAAS;aACF,GAAO;AACd,aAAQ,MAAM,wBAAwB,EAAM;;KAoBM;GAAE,WAAU;aAA9D;IAEE,kBAAC,GAAD;KAAa,OAAM;eAAnB,CACE,kBAAC,GAAD;MAAW,OAAM;MAAQ,UAAA;MAAS,OAAO,EAAO,OAAO;gBACrD,kBAAC,GAAD;OACE,MAAK;OACL,GAAI,EAAS,QAAQ;OACrB,aAAY;OACZ,gBAAc,CAAC,CAAC,EAAO;OACvB,UAAU;OACV,CAAA;MACQ,CAAA,EAEZ,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAW,OAAM;MAAa,UAAA;MAAS,OAAO,EAAO,WAAW;gBAC9D,kBAAC,GAAD;OACE,GAAI,EAAS,YAAY;OACzB,aAAY;OACZ,gBAAc,CAAC,CAAC,EAAO;OACvB,CAAA;MACQ,CAAA,EAEZ,kBAAC,GAAD;MAAW,OAAM;MAAY,UAAA;MAAS,OAAO,EAAO,UAAU;gBAC5D,kBAAC,GAAD;OAAO,GAAI,EAAS,WAAW;OAAE,aAAY;OAAM,gBAAc,CAAC,CAAC,EAAO;OAAY,CAAA;MAC5E,CAAA,CACJ,EAAA,CAAA,CACE;;IAGd,kBAAC,GAAD;KAAa,OAAM;eAAnB,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;MAAW,OAAM;MAAO,UAAA;gBACtB,kBAAC,GAAD;OAAa,GAAI,EAAS,OAAO;OAAE,SAAS;OAAe,CAAA;MACjD,CAAA,EAEX,KACC,kBAAC,GAAD;MAAW,OAAM;gBACf,kBAAC,GAAD;OAAa,GAAI,EAAS,SAAS;OAAE,SAAS;OAAiB,CAAA;MACrD,CAAA,CAEN,EAAA,CAAA,EAET,EAAQ,SAAS,KAChB,kBAAC,GAAD;MAAW,OAAM;MAAS,aAAY;gBACpC,kBAAC,GAAD;OACE,GAAI,EAAS,WAAW;OACxB,SAAS,EAAQ,KAAK,OAAO;QAAE,OAAO,EAAE;QAAI,OAAO,EAAE;QAAM,EAAE;OAC7D,aAAY;OACZ,CAAA;MACQ,CAAA,CAEF;;IAGb,CAAC,KACA,kBAAC,GAAD;KAAa,OAAM;eACjB,kBAAC,GAAD;MACE,MAAK;MACI;MACT,SAAS,EAAE,eACT,kBAAC,GAAD;OACE,OAAM;OACN,SAAS,EAAM,SAAS;OACxB,UAAU,EAAM;OAChB,aAAY;OACZ,CAAA;MAEJ,CAAA;KACU,CAAA;IAEX;;EACG,CAAA"}