@burdenoff/microfe-admin 2026.608.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 (80) hide show
  1. package/dist/admin/AdminRoutes.js +14 -10
  2. package/dist/admin/AdminRoutes.js.map +1 -1
  3. package/dist/admin/components/AccessDenied.js +2 -2
  4. package/dist/admin/components/AccessDenied.js.map +1 -1
  5. package/dist/admin/components/ActivityFeed.js +4 -4
  6. package/dist/admin/components/ActivityFeed.js.map +1 -1
  7. package/dist/admin/components/Charts.js +4 -4
  8. package/dist/admin/components/Charts.js.map +1 -1
  9. package/dist/admin/components/ConfirmDialog.js +44 -25
  10. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  11. package/dist/admin/components/DataTable.js +7 -7
  12. package/dist/admin/components/DataTable.js.map +1 -1
  13. package/dist/admin/components/FormSheet.js +7 -7
  14. package/dist/admin/components/FormSheet.js.map +1 -1
  15. package/dist/admin/components/MetricCard.js +4 -4
  16. package/dist/admin/components/MetricCard.js.map +1 -1
  17. package/dist/admin/components/PageLayout.js +3 -3
  18. package/dist/admin/components/PageLayout.js.map +1 -1
  19. package/dist/admin/components/PermissionGuard.js +1 -1
  20. package/dist/admin/components/PermissionGuard.js.map +1 -1
  21. package/dist/admin/components/RowActions.js +2 -2
  22. package/dist/admin/components/RowActions.js.map +1 -1
  23. package/dist/admin/components/TenantMembersPanel.js +15 -15
  24. package/dist/admin/components/TenantMembersPanel.js.map +1 -1
  25. package/dist/admin/components/TenantSsoPanel.js +14 -14
  26. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  27. package/dist/admin/components/Toast.js +9 -9
  28. package/dist/admin/components/Toast.js.map +1 -1
  29. package/dist/admin/components/forms/SubscriptionForm.js +10 -10
  30. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  31. package/dist/admin/components/forms/TenantForm.js +8 -8
  32. package/dist/admin/components/forms/TenantForm.js.map +1 -1
  33. package/dist/admin/components/forms/TicketForm.js +2 -2
  34. package/dist/admin/components/forms/TicketForm.js.map +1 -1
  35. package/dist/admin/components/forms/UserForm.js +8 -8
  36. package/dist/admin/components/forms/UserForm.js.map +1 -1
  37. package/dist/admin/components/ui.js +16 -16
  38. package/dist/admin/components/ui.js.map +1 -1
  39. package/dist/admin/constants/index.js +8 -8
  40. package/dist/admin/constants/index.js.map +1 -1
  41. package/dist/admin/pages/AuditLogsPage.js +8 -8
  42. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  43. package/dist/admin/pages/CommunicationsPage.js +2 -2
  44. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  45. package/dist/admin/pages/ContentPagesPage.js +661 -0
  46. package/dist/admin/pages/ContentPagesPage.js.map +1 -0
  47. package/dist/admin/pages/FeatureFlagsPage.js +4 -4
  48. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  49. package/dist/admin/pages/OrganizationsListPage.js +6 -6
  50. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  51. package/dist/admin/pages/ProductsPage.js +7 -7
  52. package/dist/admin/pages/ProductsPage.js.map +1 -1
  53. package/dist/admin/pages/ReferralsPage.js +23 -23
  54. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  55. package/dist/admin/pages/RolesPermissionsPage.js +8 -8
  56. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  57. package/dist/admin/pages/SREPage.js +8 -8
  58. package/dist/admin/pages/SREPage.js.map +1 -1
  59. package/dist/admin/pages/SandboxSettingsPage.js +20 -20
  60. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  61. package/dist/admin/pages/SettingsPage.js +20 -20
  62. package/dist/admin/pages/SettingsPage.js.map +1 -1
  63. package/dist/admin/pages/SyncFailuresPage.js +11 -11
  64. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  65. package/dist/admin/pages/TenantDetailPage.js +22 -22
  66. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  67. package/dist/admin/pages/TenantFormRoutePage.js +2 -2
  68. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  69. package/dist/admin/pages/TenantsListPage.js +5 -5
  70. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  71. package/dist/admin/pages/UsersListPage.js +6 -6
  72. package/dist/admin/pages/UsersListPage.js.map +1 -1
  73. package/dist/admin/pages/WaitlistPage.js +7 -7
  74. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  75. package/dist/admin/pages/WorkflowsPage.js +3 -3
  76. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  77. package/dist/admin/pages/WorkspacesListPage.js +8 -8
  78. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  79. package/dist/admin/pages/index.js +1 -0
  80. package/package.json +1 -1
@@ -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":"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"}
@@ -4,7 +4,7 @@ import { jsx as n, jsxs as r } from "react/jsx-runtime";
4
4
  //#region src/admin/components/ui.tsx
5
5
  var i = t(({ className: t, children: r, ...i }, a) => /* @__PURE__ */ n("div", {
6
6
  ref: a,
7
- className: e("rounded-lg border border-border bg-card text-card-foreground shadow-sm", t),
7
+ className: e("rounded-lg border border-border-subtle bg-bg-surface text-text-primary shadow-sm", t),
8
8
  ...i,
9
9
  children: r
10
10
  }));
@@ -25,7 +25,7 @@ var o = t(({ className: t, children: r, ...i }, a) => /* @__PURE__ */ n("h3", {
25
25
  o.displayName = "CardTitle";
26
26
  var s = t(({ className: t, children: r, ...i }, a) => /* @__PURE__ */ n("p", {
27
27
  ref: a,
28
- className: e("text-sm text-muted-foreground", t),
28
+ className: e("text-sm text-text-secondary", t),
29
29
  ...i,
30
30
  children: r
31
31
  }));
@@ -45,12 +45,12 @@ var l = t(({ className: t, children: r, ...i }, a) => /* @__PURE__ */ n("div", {
45
45
  }));
46
46
  l.displayName = "CardFooter";
47
47
  var u = {
48
- default: "bg-primary text-primary-foreground",
48
+ default: "bg-action-primary-bg text-action-primary-text",
49
49
  secondary: "bg-secondary text-secondary-foreground",
50
50
  success: "bg-success/10 text-success border-success/20",
51
51
  warning: "bg-warning/10 text-warning border-warning/20",
52
- destructive: "bg-destructive/10 text-destructive border-destructive/20",
53
- outline: "border-border bg-transparent text-foreground"
52
+ destructive: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20",
53
+ outline: "border-border-subtle bg-transparent text-text-primary"
54
54
  }, d = t(({ className: t, variant: r = "default", children: i, ...a }, o) => /* @__PURE__ */ n("span", {
55
55
  ref: o,
56
56
  className: e("inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors", u[r], t),
@@ -59,11 +59,11 @@ var u = {
59
59
  }));
60
60
  d.displayName = "Badge";
61
61
  var f = {
62
- default: "bg-primary text-primary-foreground hover:bg-primary/90",
62
+ default: "bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90",
63
63
  secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
64
- outline: "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
65
- ghost: "hover:bg-accent hover:text-accent-foreground",
66
- destructive: "bg-destructive text-destructive-foreground hover:bg-destructive/90",
64
+ outline: "border border-border-default bg-bg-surface hover:bg-accent hover:text-text-primary",
65
+ ghost: "hover:bg-accent hover:text-text-primary",
66
+ destructive: "bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90",
67
67
  link: "text-primary underline-offset-4 hover:underline"
68
68
  }, p = {
69
69
  default: "h-10 px-4 py-2",
@@ -72,21 +72,21 @@ var f = {
72
72
  icon: "h-10 w-10"
73
73
  }, m = t(({ className: t, variant: r = "default", size: i = "default", children: a, ...o }, s) => /* @__PURE__ */ n("button", {
74
74
  ref: s,
75
- className: e("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "disabled:pointer-events-none disabled:opacity-50", f[r], p[i], t),
75
+ className: e("inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2", "disabled:pointer-events-none disabled:opacity-50", f[r], p[i], t),
76
76
  ...o,
77
77
  children: a
78
78
  }));
79
79
  m.displayName = "Button";
80
80
  var h = t(({ className: t, type: r, ...i }, a) => /* @__PURE__ */ n("input", {
81
81
  type: r,
82
- className: e("flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background", "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground", "placeholder:text-muted-foreground", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2", "disabled:cursor-not-allowed disabled:opacity-50", t),
82
+ className: e("flex h-10 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background", "file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-text-primary", "placeholder:text-text-secondary", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2", "disabled:cursor-not-allowed disabled:opacity-50", t),
83
83
  ref: a,
84
84
  ...i
85
85
  }));
86
86
  h.displayName = "Input";
87
87
  var g = t(({ className: t, ...r }, i) => /* @__PURE__ */ n("div", {
88
88
  ref: i,
89
- className: e("animate-pulse rounded-md bg-muted", t),
89
+ className: e("animate-pulse rounded-md bg-bg-sunken", t),
90
90
  ...r
91
91
  }));
92
92
  g.displayName = "Skeleton";
@@ -102,14 +102,14 @@ var v = {
102
102
  lg: "h-12 w-12 text-base"
103
103
  }, y = t(({ className: t, src: r, alt: i, fallback: a, size: o = "default", ...s }, c) => /* @__PURE__ */ n("div", {
104
104
  ref: c,
105
- className: e("relative flex shrink-0 overflow-hidden rounded-full bg-muted", v[o], t),
105
+ className: e("relative flex shrink-0 overflow-hidden rounded-full bg-bg-sunken", v[o], t),
106
106
  ...s,
107
107
  children: r ? /* @__PURE__ */ n("img", {
108
108
  src: r,
109
109
  alt: i,
110
110
  className: "aspect-square h-full w-full object-cover"
111
111
  }) : /* @__PURE__ */ n("span", {
112
- className: "flex h-full w-full items-center justify-center font-medium text-muted-foreground",
112
+ className: "flex h-full w-full items-center justify-center font-medium text-text-secondary",
113
113
  children: a
114
114
  })
115
115
  }));
@@ -119,7 +119,7 @@ function b({ icon: e, title: t, description: i, action: a }) {
119
119
  className: "flex flex-col items-center justify-center py-12 text-center",
120
120
  children: [
121
121
  e && /* @__PURE__ */ n("div", {
122
- className: "mb-4 rounded-full bg-muted p-4 text-muted-foreground",
122
+ className: "mb-4 rounded-full bg-bg-sunken p-4 text-text-secondary",
123
123
  children: e
124
124
  }),
125
125
  /* @__PURE__ */ n("h3", {
@@ -127,7 +127,7 @@ function b({ icon: e, title: t, description: i, action: a }) {
127
127
  children: t
128
128
  }),
129
129
  i && /* @__PURE__ */ n("p", {
130
- className: "mb-4 max-w-sm text-sm text-muted-foreground",
130
+ className: "mb-4 max-w-sm text-sm text-text-secondary",
131
131
  children: i
132
132
  }),
133
133
  a
@@ -1 +1 @@
1
- {"version":3,"file":"ui.js","names":[],"sources":["../../../src/admin/components/ui.tsx"],"sourcesContent":["/**\n * Shared UI Components for Admin Console\n * Using semantic tokens for consistent theming\n */\n\nimport type { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';\nimport { forwardRef } from 'react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Card Components\n// ============================================================================\n\ninterface CardProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-border bg-card text-card-foreground shadow-sm',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n);\nCard.displayName = 'Card';\n\nexport const CardHeader = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props}>\n {children}\n </div>\n )\n);\nCardHeader.displayName = 'CardHeader';\n\nexport const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(\n ({ className, children, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn('text-2xl font-semibold leading-none tracking-tight', className)}\n {...props}\n >\n {children}\n </h3>\n )\n);\nCardTitle.displayName = 'CardTitle';\n\nexport const CardDescription = forwardRef<\n HTMLParagraphElement,\n HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => (\n <p ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props}>\n {children}\n </p>\n));\nCardDescription.displayName = 'CardDescription';\n\nexport const CardContent = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('p-6 pt-0', className)} {...props}>\n {children}\n </div>\n )\n);\nCardContent.displayName = 'CardContent';\n\nexport const CardFooter = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props}>\n {children}\n </div>\n )\n);\nCardFooter.displayName = 'CardFooter';\n\n// ============================================================================\n// Badge Component\n// ============================================================================\n\ninterface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n variant?: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline';\n}\n\nconst badgeVariants = {\n default: 'bg-primary text-primary-foreground',\n secondary: 'bg-secondary text-secondary-foreground',\n success: 'bg-success/10 text-success border-success/20',\n warning: 'bg-warning/10 text-warning border-warning/20',\n destructive: 'bg-destructive/10 text-destructive border-destructive/20',\n outline: 'border-border bg-transparent text-foreground',\n};\n\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, variant = 'default', children, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',\n badgeVariants[variant],\n className\n )}\n {...props}\n >\n {children}\n </span>\n )\n);\nBadge.displayName = 'Badge';\n\n// ============================================================================\n// Button Component\n// ============================================================================\n\ninterface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link';\n size?: 'default' | 'sm' | 'lg' | 'icon';\n}\n\nconst buttonVariants = {\n default: 'bg-primary text-primary-foreground hover:bg-primary/90',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',\n ghost: 'hover:bg-accent hover:text-accent-foreground',\n destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',\n link: 'text-primary underline-offset-4 hover:underline',\n};\n\nconst buttonSizes = {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'default', size = 'default', children, ...props }, ref) => (\n <button\n ref={ref}\n className={cn(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:pointer-events-none disabled:opacity-50',\n buttonVariants[variant],\n buttonSizes[size],\n className\n )}\n {...props}\n >\n {children}\n </button>\n )\n);\nButton.displayName = 'Button';\n\n// ============================================================================\n// Input Component\n// ============================================================================\n\ntype InputProps = React.InputHTMLAttributes<HTMLInputElement>;\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => (\n <input\n type={type}\n className={cn(\n 'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background',\n 'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground',\n 'placeholder:text-muted-foreground',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n ref={ref}\n {...props}\n />\n )\n);\nInput.displayName = 'Input';\n\n// ============================================================================\n// Skeleton Component\n// ============================================================================\n\ntype SkeletonProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('animate-pulse rounded-md bg-muted', className)} {...props} />\n )\n);\nSkeleton.displayName = 'Skeleton';\n\n// ============================================================================\n// Separator Component\n// ============================================================================\n\ninterface SeparatorProps extends HTMLAttributes<HTMLDivElement> {\n orientation?: 'horizontal' | 'vertical';\n}\n\nexport const Separator = forwardRef<HTMLDivElement, SeparatorProps>(\n ({ className, orientation = 'horizontal', ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'shrink-0 bg-border',\n orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',\n className\n )}\n {...props}\n />\n )\n);\nSeparator.displayName = 'Separator';\n\n// ============================================================================\n// Avatar Component\n// ============================================================================\n\ninterface AvatarProps extends HTMLAttributes<HTMLDivElement> {\n src?: string;\n alt?: string;\n fallback: string;\n size?: 'sm' | 'default' | 'lg';\n}\n\nconst avatarSizes = {\n sm: 'h-8 w-8 text-xs',\n default: 'h-10 w-10 text-sm',\n lg: 'h-12 w-12 text-base',\n};\n\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>(\n ({ className, src, alt, fallback, size = 'default', ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'relative flex shrink-0 overflow-hidden rounded-full bg-muted',\n avatarSizes[size],\n className\n )}\n {...props}\n >\n {src ? (\n <img src={src} alt={alt} className=\"aspect-square h-full w-full object-cover\" />\n ) : (\n <span className=\"flex h-full w-full items-center justify-center font-medium text-muted-foreground\">\n {fallback}\n </span>\n )}\n </div>\n )\n);\nAvatar.displayName = 'Avatar';\n\n// ============================================================================\n// Empty State Component\n// ============================================================================\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}\n\nexport function EmptyState({ icon, title, description, action }: EmptyStateProps) {\n return (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n {icon && <div className=\"mb-4 rounded-full bg-muted p-4 text-muted-foreground\">{icon}</div>}\n <h3 className=\"mb-2 text-lg font-semibold\">{title}</h3>\n {description && <p className=\"mb-4 max-w-sm text-sm text-muted-foreground\">{description}</p>}\n {action}\n </div>\n );\n}\n\n// ============================================================================\n// Loading Spinner Component\n// ============================================================================\n\ninterface SpinnerProps extends HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'default' | 'lg';\n}\n\nconst spinnerSizes = {\n sm: 'h-4 w-4',\n default: 'h-6 w-6',\n lg: 'h-8 w-8',\n};\n\nexport function Spinner({ className, size = 'default', ...props }: SpinnerProps) {\n return (\n <div\n className={cn(\n 'animate-spin rounded-full border-2 border-current border-t-transparent text-primary',\n spinnerSizes[size],\n className\n )}\n {...props}\n />\n );\n}\n\n// ============================================================================\n// Page Loading Fallback\n// ============================================================================\n\ninterface PageLoadingFallbackProps {\n message?: string;\n}\n\nexport function PageLoadingFallback({ message = 'Loading...' }: PageLoadingFallbackProps) {\n return (\n <div className=\"flex h-full min-h-[400px] flex-col items-center justify-center gap-4\">\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-muted-foreground\">{message}</p>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ui.js","names":[],"sources":["../../../src/admin/components/ui.tsx"],"sourcesContent":["/**\n * Shared UI Components for Admin Console\n * Using semantic tokens for consistent theming\n */\n\nimport type { ReactNode, HTMLAttributes, ButtonHTMLAttributes } from 'react';\nimport { forwardRef } from 'react';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Card Components\n// ============================================================================\n\ninterface CardProps extends HTMLAttributes<HTMLDivElement> {\n children: ReactNode;\n}\n\nexport const Card = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-border-subtle bg-bg-surface text-text-primary shadow-sm',\n className\n )}\n {...props}\n >\n {children}\n </div>\n )\n);\nCard.displayName = 'Card';\n\nexport const CardHeader = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props}>\n {children}\n </div>\n )\n);\nCardHeader.displayName = 'CardHeader';\n\nexport const CardTitle = forwardRef<HTMLHeadingElement, HTMLAttributes<HTMLHeadingElement>>(\n ({ className, children, ...props }, ref) => (\n <h3\n ref={ref}\n className={cn('text-2xl font-semibold leading-none tracking-tight', className)}\n {...props}\n >\n {children}\n </h3>\n )\n);\nCardTitle.displayName = 'CardTitle';\n\nexport const CardDescription = forwardRef<\n HTMLParagraphElement,\n HTMLAttributes<HTMLParagraphElement>\n>(({ className, children, ...props }, ref) => (\n <p ref={ref} className={cn('text-sm text-text-secondary', className)} {...props}>\n {children}\n </p>\n));\nCardDescription.displayName = 'CardDescription';\n\nexport const CardContent = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('p-6 pt-0', className)} {...props}>\n {children}\n </div>\n )\n);\nCardContent.displayName = 'CardContent';\n\nexport const CardFooter = forwardRef<HTMLDivElement, CardProps>(\n ({ className, children, ...props }, ref) => (\n <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props}>\n {children}\n </div>\n )\n);\nCardFooter.displayName = 'CardFooter';\n\n// ============================================================================\n// Badge Component\n// ============================================================================\n\ninterface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n variant?: 'default' | 'secondary' | 'success' | 'warning' | 'destructive' | 'outline';\n}\n\nconst badgeVariants = {\n default: 'bg-action-primary-bg text-action-primary-text',\n secondary: 'bg-secondary text-secondary-foreground',\n success: 'bg-success/10 text-success border-success/20',\n warning: 'bg-warning/10 text-warning border-warning/20',\n destructive: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n outline: 'border-border-subtle bg-transparent text-text-primary',\n};\n\nexport const Badge = forwardRef<HTMLSpanElement, BadgeProps>(\n ({ className, variant = 'default', children, ...props }, ref) => (\n <span\n ref={ref}\n className={cn(\n 'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors',\n badgeVariants[variant],\n className\n )}\n {...props}\n >\n {children}\n </span>\n )\n);\nBadge.displayName = 'Badge';\n\n// ============================================================================\n// Button Component\n// ============================================================================\n\ninterface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' | 'link';\n size?: 'default' | 'sm' | 'lg' | 'icon';\n}\n\nconst buttonVariants = {\n default: 'bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg/90',\n secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',\n outline: 'border border-border-default bg-bg-surface hover:bg-accent hover:text-text-primary',\n ghost: 'hover:bg-accent hover:text-text-primary',\n destructive: 'bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90',\n link: 'text-primary underline-offset-4 hover:underline',\n};\n\nconst buttonSizes = {\n default: 'h-10 px-4 py-2',\n sm: 'h-9 rounded-md px-3',\n lg: 'h-11 rounded-md px-8',\n icon: 'h-10 w-10',\n};\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n ({ className, variant = 'default', size = 'default', children, ...props }, ref) => (\n <button\n ref={ref}\n className={cn(\n 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2',\n 'disabled:pointer-events-none disabled:opacity-50',\n buttonVariants[variant],\n buttonSizes[size],\n className\n )}\n {...props}\n >\n {children}\n </button>\n )\n);\nButton.displayName = 'Button';\n\n// ============================================================================\n// Input Component\n// ============================================================================\n\ntype InputProps = React.InputHTMLAttributes<HTMLInputElement>;\n\nexport const Input = forwardRef<HTMLInputElement, InputProps>(\n ({ className, type, ...props }, ref) => (\n <input\n type={type}\n className={cn(\n 'flex h-10 w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 text-sm ring-offset-background',\n 'file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-text-primary',\n 'placeholder:text-text-secondary',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2',\n 'disabled:cursor-not-allowed disabled:opacity-50',\n className\n )}\n ref={ref}\n {...props}\n />\n )\n);\nInput.displayName = 'Input';\n\n// ============================================================================\n// Skeleton Component\n// ============================================================================\n\ntype SkeletonProps = HTMLAttributes<HTMLDivElement>;\n\nexport const Skeleton = forwardRef<HTMLDivElement, SkeletonProps>(\n ({ className, ...props }, ref) => (\n <div ref={ref} className={cn('animate-pulse rounded-md bg-bg-sunken', className)} {...props} />\n )\n);\nSkeleton.displayName = 'Skeleton';\n\n// ============================================================================\n// Separator Component\n// ============================================================================\n\ninterface SeparatorProps extends HTMLAttributes<HTMLDivElement> {\n orientation?: 'horizontal' | 'vertical';\n}\n\nexport const Separator = forwardRef<HTMLDivElement, SeparatorProps>(\n ({ className, orientation = 'horizontal', ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'shrink-0 bg-border',\n orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',\n className\n )}\n {...props}\n />\n )\n);\nSeparator.displayName = 'Separator';\n\n// ============================================================================\n// Avatar Component\n// ============================================================================\n\ninterface AvatarProps extends HTMLAttributes<HTMLDivElement> {\n src?: string;\n alt?: string;\n fallback: string;\n size?: 'sm' | 'default' | 'lg';\n}\n\nconst avatarSizes = {\n sm: 'h-8 w-8 text-xs',\n default: 'h-10 w-10 text-sm',\n lg: 'h-12 w-12 text-base',\n};\n\nexport const Avatar = forwardRef<HTMLDivElement, AvatarProps>(\n ({ className, src, alt, fallback, size = 'default', ...props }, ref) => (\n <div\n ref={ref}\n className={cn(\n 'relative flex shrink-0 overflow-hidden rounded-full bg-bg-sunken',\n avatarSizes[size],\n className\n )}\n {...props}\n >\n {src ? (\n <img src={src} alt={alt} className=\"aspect-square h-full w-full object-cover\" />\n ) : (\n <span className=\"flex h-full w-full items-center justify-center font-medium text-text-secondary\">\n {fallback}\n </span>\n )}\n </div>\n )\n);\nAvatar.displayName = 'Avatar';\n\n// ============================================================================\n// Empty State Component\n// ============================================================================\n\ninterface EmptyStateProps {\n icon?: ReactNode;\n title: string;\n description?: string;\n action?: ReactNode;\n}\n\nexport function EmptyState({ icon, title, description, action }: EmptyStateProps) {\n return (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n {icon && <div className=\"mb-4 rounded-full bg-bg-sunken p-4 text-text-secondary\">{icon}</div>}\n <h3 className=\"mb-2 text-lg font-semibold\">{title}</h3>\n {description && <p className=\"mb-4 max-w-sm text-sm text-text-secondary\">{description}</p>}\n {action}\n </div>\n );\n}\n\n// ============================================================================\n// Loading Spinner Component\n// ============================================================================\n\ninterface SpinnerProps extends HTMLAttributes<HTMLDivElement> {\n size?: 'sm' | 'default' | 'lg';\n}\n\nconst spinnerSizes = {\n sm: 'h-4 w-4',\n default: 'h-6 w-6',\n lg: 'h-8 w-8',\n};\n\nexport function Spinner({ className, size = 'default', ...props }: SpinnerProps) {\n return (\n <div\n className={cn(\n 'animate-spin rounded-full border-2 border-current border-t-transparent text-primary',\n spinnerSizes[size],\n className\n )}\n {...props}\n />\n );\n}\n\n// ============================================================================\n// Page Loading Fallback\n// ============================================================================\n\ninterface PageLoadingFallbackProps {\n message?: string;\n}\n\nexport function PageLoadingFallback({ message = 'Loading...' }: PageLoadingFallbackProps) {\n return (\n <div className=\"flex h-full min-h-[400px] flex-col items-center justify-center gap-4\">\n <Spinner size=\"lg\" />\n <p className=\"text-sm text-text-secondary\">{message}</p>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -6,7 +6,7 @@ var e = {
6
6
  },
7
7
  suspended: {
8
8
  label: "Suspended",
9
- color: "bg-destructive/10 text-destructive border-destructive/20"
9
+ color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
10
10
  },
11
11
  trial: {
12
12
  label: "Trial",
@@ -14,7 +14,7 @@ var e = {
14
14
  },
15
15
  deleted: {
16
16
  label: "Deleted",
17
- color: "bg-muted text-muted-foreground border-muted"
17
+ color: "bg-bg-sunken text-text-secondary border-muted"
18
18
  },
19
19
  pending: {
20
20
  label: "Pending",
@@ -31,11 +31,11 @@ var e = {
31
31
  },
32
32
  inactive: {
33
33
  label: "Inactive",
34
- color: "bg-muted text-muted-foreground border-muted"
34
+ color: "bg-bg-sunken text-text-secondary border-muted"
35
35
  },
36
36
  blocked: {
37
37
  label: "Blocked",
38
- color: "bg-destructive/10 text-destructive border-destructive/20"
38
+ color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
39
39
  },
40
40
  pending: {
41
41
  label: "Pending",
@@ -52,16 +52,16 @@ var e = {
52
52
  },
53
53
  down: {
54
54
  label: "Down",
55
- color: "bg-destructive/10 text-destructive border-destructive/20"
55
+ color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
56
56
  },
57
57
  unknown: {
58
58
  label: "Unknown",
59
- color: "bg-muted text-muted-foreground border-muted"
59
+ color: "bg-bg-sunken text-text-secondary border-muted"
60
60
  }
61
61
  }, r = {
62
62
  pending: {
63
63
  label: "Pending",
64
- color: "bg-muted text-muted-foreground border-muted"
64
+ color: "bg-bg-sunken text-text-secondary border-muted"
65
65
  },
66
66
  running: {
67
67
  label: "Running",
@@ -77,7 +77,7 @@ var e = {
77
77
  },
78
78
  failed: {
79
79
  label: "Failed",
80
- color: "bg-destructive/10 text-destructive border-destructive/20"
80
+ color: "bg-status-error-bg/10 text-status-error-text border-status-error-border/20"
81
81
  },
82
82
  rolled_back: {
83
83
  label: "Rolled Back",
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/admin/constants/index.ts"],"sourcesContent":["import type {\n TenantStatus,\n UserStatus,\n SubscriptionStatus,\n TicketStatus,\n TicketPriority,\n HealthStatus,\n DeploymentStatus,\n CloudProvider,\n} from '../types';\n\n/**\n * Tenant status labels and colors\n */\nexport const TENANT_STATUS_CONFIG: Record<TenantStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n suspended: {\n label: 'Suspended',\n color: 'bg-destructive/10 text-destructive border-destructive/20',\n },\n trial: { label: 'Trial', color: 'bg-warning/10 text-warning border-warning/20' },\n deleted: { label: 'Deleted', color: 'bg-muted text-muted-foreground border-muted' },\n pending: { label: 'Pending', color: 'bg-info/10 text-info border-info/20' },\n provisioning: { label: 'Provisioning', color: 'bg-amber-100 text-amber-700 border-amber-200' },\n};\n\n/**\n * User status labels and colors\n */\nexport const USER_STATUS_CONFIG: Record<UserStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n inactive: { label: 'Inactive', color: 'bg-muted text-muted-foreground border-muted' },\n blocked: { label: 'Blocked', color: 'bg-destructive/10 text-destructive border-destructive/20' },\n pending: { label: 'Pending', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Subscription status labels and colors\n */\nexport const SUBSCRIPTION_STATUS_CONFIG: Record<\n SubscriptionStatus,\n { label: string; color: string }\n> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n canceled: { label: 'Canceled', color: 'bg-muted text-muted-foreground border-muted' },\n past_due: {\n label: 'Past Due',\n color: 'bg-destructive/10 text-destructive border-destructive/20',\n },\n trialing: { label: 'Trialing', color: 'bg-info/10 text-info border-info/20' },\n paused: { label: 'Paused', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Ticket status labels and colors\n */\nexport const TICKET_STATUS_CONFIG: Record<TicketStatus, { label: string; color: string }> = {\n open: { label: 'Open', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-warning/10 text-warning border-warning/20' },\n resolved: { label: 'Resolved', color: 'bg-success/10 text-success border-success/20' },\n closed: { label: 'Closed', color: 'bg-muted text-muted-foreground border-muted' },\n};\n\n/**\n * Ticket priority labels and colors\n */\nexport const TICKET_PRIORITY_CONFIG: Record<TicketPriority, { label: string; color: string }> = {\n low: { label: 'Low', color: 'bg-muted text-muted-foreground border-muted' },\n medium: { label: 'Medium', color: 'bg-info/10 text-info border-info/20' },\n high: { label: 'High', color: 'bg-warning/10 text-warning border-warning/20' },\n critical: {\n label: 'Critical',\n color: 'bg-destructive/10 text-destructive border-destructive/20',\n },\n};\n\n/**\n * Health status labels and colors\n */\nexport const HEALTH_STATUS_CONFIG: Record<HealthStatus, { label: string; color: string }> = {\n healthy: { label: 'Healthy', color: 'bg-success/10 text-success border-success/20' },\n degraded: { label: 'Degraded', color: 'bg-warning/10 text-warning border-warning/20' },\n down: { label: 'Down', color: 'bg-destructive/10 text-destructive border-destructive/20' },\n unknown: { label: 'Unknown', color: 'bg-muted text-muted-foreground border-muted' },\n};\n\n/**\n * Deployment status labels and colors\n */\nexport const DEPLOYMENT_STATUS_CONFIG: Record<DeploymentStatus, { label: string; color: string }> =\n {\n pending: { label: 'Pending', color: 'bg-muted text-muted-foreground border-muted' },\n running: { label: 'Running', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-info/10 text-info border-info/20' },\n success: { label: 'Success', color: 'bg-success/10 text-success border-success/20' },\n failed: { label: 'Failed', color: 'bg-destructive/10 text-destructive border-destructive/20' },\n rolled_back: { label: 'Rolled Back', color: 'bg-warning/10 text-warning border-warning/20' },\n };\n\n/**\n * Cloud provider labels and colors\n */\nexport const CLOUD_PROVIDER_CONFIG: Record<\n CloudProvider,\n { label: string; color: string; icon: string }\n> = {\n aws: {\n label: 'AWS',\n color: 'bg-orange-500/10 text-orange-500 border-orange-500/20',\n icon: 'aws',\n },\n azure: {\n label: 'Azure',\n color: 'bg-blue-500/10 text-blue-500 border-blue-500/20',\n icon: 'azure',\n },\n gcp: { label: 'GCP', color: 'bg-red-500/10 text-red-500 border-red-500/20', icon: 'gcp' },\n digitalocean: {\n label: 'DigitalOcean',\n color: 'bg-cyan-500/10 text-cyan-500 border-cyan-500/20',\n icon: 'do',\n },\n};\n\n/**\n * Chart colors using semantic tokens\n */\nexport const CHART_COLORS = {\n primary: 'hsl(var(--chart-1))',\n secondary: 'hsl(var(--chart-2))',\n tertiary: 'hsl(var(--chart-3))',\n quaternary: 'hsl(var(--chart-4))',\n quinary: 'hsl(var(--chart-5))',\n};\n\n/**\n * Default pagination\n */\nexport const DEFAULT_PAGE_SIZE = 20;\nexport const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];\n"],"mappings":";AAcA,IAAa,IAA+E;CAC1F,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,WAAW;EACT,OAAO;EACP,OAAO;EACR;CACD,OAAO;EAAE,OAAO;EAAS,OAAO;EAAgD;CAChF,SAAS;EAAE,OAAO;EAAW,OAAO;EAA+C;CACnF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,cAAc;EAAE,OAAO;EAAgB,OAAO;EAAgD;CAC/F,EAKY,IAA2E;CACtF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,UAAU;EAAE,OAAO;EAAY,OAAO;EAA+C;CACrF,SAAS;EAAE,OAAO;EAAW,OAAO;EAA4D;CAChG,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACrF,EA6CY,IAA+E;CAC1F,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,UAAU;EAAE,OAAO;EAAY,OAAO;EAAgD;CACtF,MAAM;EAAE,OAAO;EAAQ,OAAO;EAA4D;CAC1F,SAAS;EAAE,OAAO;EAAW,OAAO;EAA+C;CACpF,EAKY,IACX;CACE,SAAS;EAAE,OAAO;EAAW,OAAO;EAA+C;CACnF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,aAAa;EAAE,OAAO;EAAe,OAAO;EAAuC;CACnF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAA4D;CAC9F,aAAa;EAAE,OAAO;EAAe,OAAO;EAAgD;CAC7F"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/admin/constants/index.ts"],"sourcesContent":["import type {\n TenantStatus,\n UserStatus,\n SubscriptionStatus,\n TicketStatus,\n TicketPriority,\n HealthStatus,\n DeploymentStatus,\n CloudProvider,\n} from '../types';\n\n/**\n * Tenant status labels and colors\n */\nexport const TENANT_STATUS_CONFIG: Record<TenantStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n suspended: {\n label: 'Suspended',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n trial: { label: 'Trial', color: 'bg-warning/10 text-warning border-warning/20' },\n deleted: { label: 'Deleted', color: 'bg-bg-sunken text-text-secondary border-muted' },\n pending: { label: 'Pending', color: 'bg-info/10 text-info border-info/20' },\n provisioning: { label: 'Provisioning', color: 'bg-amber-100 text-amber-700 border-amber-200' },\n};\n\n/**\n * User status labels and colors\n */\nexport const USER_STATUS_CONFIG: Record<UserStatus, { label: string; color: string }> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n inactive: { label: 'Inactive', color: 'bg-bg-sunken text-text-secondary border-muted' },\n blocked: { label: 'Blocked', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n pending: { label: 'Pending', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Subscription status labels and colors\n */\nexport const SUBSCRIPTION_STATUS_CONFIG: Record<\n SubscriptionStatus,\n { label: string; color: string }\n> = {\n active: { label: 'Active', color: 'bg-success/10 text-success border-success/20' },\n canceled: { label: 'Canceled', color: 'bg-bg-sunken text-text-secondary border-muted' },\n past_due: {\n label: 'Past Due',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n trialing: { label: 'Trialing', color: 'bg-info/10 text-info border-info/20' },\n paused: { label: 'Paused', color: 'bg-warning/10 text-warning border-warning/20' },\n};\n\n/**\n * Ticket status labels and colors\n */\nexport const TICKET_STATUS_CONFIG: Record<TicketStatus, { label: string; color: string }> = {\n open: { label: 'Open', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-warning/10 text-warning border-warning/20' },\n resolved: { label: 'Resolved', color: 'bg-success/10 text-success border-success/20' },\n closed: { label: 'Closed', color: 'bg-bg-sunken text-text-secondary border-muted' },\n};\n\n/**\n * Ticket priority labels and colors\n */\nexport const TICKET_PRIORITY_CONFIG: Record<TicketPriority, { label: string; color: string }> = {\n low: { label: 'Low', color: 'bg-bg-sunken text-text-secondary border-muted' },\n medium: { label: 'Medium', color: 'bg-info/10 text-info border-info/20' },\n high: { label: 'High', color: 'bg-warning/10 text-warning border-warning/20' },\n critical: {\n label: 'Critical',\n color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20',\n },\n};\n\n/**\n * Health status labels and colors\n */\nexport const HEALTH_STATUS_CONFIG: Record<HealthStatus, { label: string; color: string }> = {\n healthy: { label: 'Healthy', color: 'bg-success/10 text-success border-success/20' },\n degraded: { label: 'Degraded', color: 'bg-warning/10 text-warning border-warning/20' },\n down: { label: 'Down', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n unknown: { label: 'Unknown', color: 'bg-bg-sunken text-text-secondary border-muted' },\n};\n\n/**\n * Deployment status labels and colors\n */\nexport const DEPLOYMENT_STATUS_CONFIG: Record<DeploymentStatus, { label: string; color: string }> =\n {\n pending: { label: 'Pending', color: 'bg-bg-sunken text-text-secondary border-muted' },\n running: { label: 'Running', color: 'bg-info/10 text-info border-info/20' },\n in_progress: { label: 'In Progress', color: 'bg-info/10 text-info border-info/20' },\n success: { label: 'Success', color: 'bg-success/10 text-success border-success/20' },\n failed: { label: 'Failed', color: 'bg-status-error-bg/10 text-status-error-text border-status-error-border/20' },\n rolled_back: { label: 'Rolled Back', color: 'bg-warning/10 text-warning border-warning/20' },\n };\n\n/**\n * Cloud provider labels and colors\n */\nexport const CLOUD_PROVIDER_CONFIG: Record<\n CloudProvider,\n { label: string; color: string; icon: string }\n> = {\n aws: {\n label: 'AWS',\n color: 'bg-orange-500/10 text-orange-500 border-orange-500/20',\n icon: 'aws',\n },\n azure: {\n label: 'Azure',\n color: 'bg-blue-500/10 text-blue-500 border-blue-500/20',\n icon: 'azure',\n },\n gcp: { label: 'GCP', color: 'bg-red-500/10 text-red-500 border-red-500/20', icon: 'gcp' },\n digitalocean: {\n label: 'DigitalOcean',\n color: 'bg-cyan-500/10 text-cyan-500 border-cyan-500/20',\n icon: 'do',\n },\n};\n\n/**\n * Chart colors using semantic tokens\n */\nexport const CHART_COLORS = {\n primary: 'hsl(var(--chart-1))',\n secondary: 'hsl(var(--chart-2))',\n tertiary: 'hsl(var(--chart-3))',\n quaternary: 'hsl(var(--chart-4))',\n quinary: 'hsl(var(--chart-5))',\n};\n\n/**\n * Default pagination\n */\nexport const DEFAULT_PAGE_SIZE = 20;\nexport const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];\n"],"mappings":";AAcA,IAAa,IAA+E;CAC1F,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,WAAW;EACT,OAAO;EACP,OAAO;EACR;CACD,OAAO;EAAE,OAAO;EAAS,OAAO;EAAgD;CAChF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACrF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,cAAc;EAAE,OAAO;EAAgB,OAAO;EAAgD;CAC/F,EAKY,IAA2E;CACtF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAAgD;CAClF,UAAU;EAAE,OAAO;EAAY,OAAO;EAAiD;CACvF,SAAS;EAAE,OAAO;EAAW,OAAO;EAA8E;CAClH,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACrF,EA6CY,IAA+E;CAC1F,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,UAAU;EAAE,OAAO;EAAY,OAAO;EAAgD;CACtF,MAAM;EAAE,OAAO;EAAQ,OAAO;EAA8E;CAC5G,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACtF,EAKY,IACX;CACE,SAAS;EAAE,OAAO;EAAW,OAAO;EAAiD;CACrF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAuC;CAC3E,aAAa;EAAE,OAAO;EAAe,OAAO;EAAuC;CACnF,SAAS;EAAE,OAAO;EAAW,OAAO;EAAgD;CACpF,QAAQ;EAAE,OAAO;EAAU,OAAO;EAA8E;CAChH,aAAa;EAAE,OAAO;EAAe,OAAO;EAAgD;CAC7F"}
@@ -107,7 +107,7 @@ function T() {
107
107
  className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
108
108
  children: [/* @__PURE__ */ b("div", {
109
109
  className: "relative max-w-sm flex-1",
110
- children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), /* @__PURE__ */ y(n, {
110
+ children: [/* @__PURE__ */ y(h, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary" }), /* @__PURE__ */ y(n, {
111
111
  placeholder: "Search by action, actor, or resource...",
112
112
  value: v,
113
113
  onChange: (e) => C(e.target.value),
@@ -123,7 +123,7 @@ function T() {
123
123
  })]
124
124
  }),
125
125
  A && /* @__PURE__ */ b("div", {
126
- className: "rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive",
126
+ className: "rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
127
127
  children: ["Failed to load audit logs: ", A.message]
128
128
  }),
129
129
  /* @__PURE__ */ y(a, {
@@ -134,10 +134,10 @@ function T() {
134
134
  render: (e) => /* @__PURE__ */ b("div", {
135
135
  className: "flex items-center gap-2",
136
136
  children: [/* @__PURE__ */ y("span", {
137
- className: "text-muted-foreground",
137
+ className: "text-text-secondary",
138
138
  children: w(e.action)
139
139
  }), /* @__PURE__ */ y("span", {
140
- className: "font-mono text-sm font-medium text-foreground",
140
+ className: "font-mono text-sm font-medium text-text-primary",
141
141
  children: e.action
142
142
  })]
143
143
  })
@@ -152,7 +152,7 @@ function T() {
152
152
  className: "truncate text-sm",
153
153
  children: e.actorId
154
154
  }), /* @__PURE__ */ y("div", {
155
- className: "text-xs text-muted-foreground capitalize",
155
+ className: "text-xs text-text-secondary capitalize",
156
156
  children: e.actorType
157
157
  })]
158
158
  })
@@ -167,7 +167,7 @@ function T() {
167
167
  className: "truncate text-sm",
168
168
  children: e.resource
169
169
  }), e.resourceId && /* @__PURE__ */ b("div", {
170
- className: "truncate text-xs text-muted-foreground",
170
+ className: "truncate text-xs text-text-secondary",
171
171
  children: ["#", String(e.resourceId).slice(-8)]
172
172
  })]
173
173
  })
@@ -177,7 +177,7 @@ function T() {
177
177
  header: "Tenant",
178
178
  width: "130px",
179
179
  render: (e) => /* @__PURE__ */ y("span", {
180
- className: "text-xs text-muted-foreground",
180
+ className: "text-xs text-text-secondary",
181
181
  children: e.tenantId ? `#${e.tenantId.slice(-8)}` : "—"
182
182
  })
183
183
  },
@@ -186,7 +186,7 @@ function T() {
186
186
  header: "IP",
187
187
  width: "120px",
188
188
  render: (e) => /* @__PURE__ */ y("span", {
189
- className: "font-mono text-xs text-muted-foreground",
189
+ className: "font-mono text-xs text-text-secondary",
190
190
  children: e.ipAddress ?? "—"
191
191
  })
192
192
  },
@@ -1 +1 @@
1
- {"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-muted-foreground\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-foreground\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-muted-foreground capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-muted-foreground\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-muted-foreground\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-muted-foreground\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-destructive/50 bg-destructive/10 p-4 text-sm text-destructive\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,IAAM,IAAY,IAEZ,IAAgD;CACpD,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;CAClC,MAAM,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;CAClC,UAAU,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;CAC1C,MAAM,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;CACpC,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;CACtC,KAAK,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;CACjC;AAED,SAAS,EAAc,GAAgB;AAErC,QAAO,EADQ,EAAO,MAAM,IAAI,CAAC,OACF,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;;AAMjE,SAAgB,IAAgB;CAC9B,IAAM,EAAE,qBAAkB,WAAW,MAAuB,GAAqB,EAE3E,CAAC,GAAM,KAAW,EAAS,EAAE,EAC7B,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAgB,KAAqB,EAAS,GAAG,EAElD,IAAc,KAAsB,CAAC,GAErC,EAAE,SAAM,YAAS,aAAU,EAAsB;EACrD,WAAW;GACT,UAAU,KAAkB,KAAA;GAC5B,OAAO;GACP,SAAS,IAAO,KAAK;GACtB;EACD,aAAa;EACb,MAAM;EACP,CAAC,EAEI,IAAY,SACF,GAAM,WAAW,SAAS,EAAE,EAC7B,KAAK,MAA8B,EAAE,KAAK,EACtD,CAAC,EAAK,CAAC,EAIJ,IAAe,QAAc;AACjC,MAAI,CAAC,EAAQ,QAAO;EACpB,IAAM,IAAI,EAAO,aAAa;AAC9B,SAAO,EAAU,QACd,MACC,EAAI,OAAO,aAAa,CAAC,SAAS,EAAE,IACpC,EAAI,QAAQ,aAAa,CAAC,SAAS,EAAE,IACrC,EAAI,SAAS,aAAa,CAAC,SAAS,EAAE,IACtC,EAAI,SAAS,aAAa,CAAC,SAAS,EAAE,CACzC;IACA,CAAC,GAAW,EAAO,CAAC,EAEjB,IAAQ,GAAM,WAAW,cAAc,GACvC,IAAa,KAAK,KAAK,IAAQ,EAAU,EAEzC,IAAoB,QAA+B;EACvD,IAAM,IAAO;AACb,MAAI,EAAK,WAAW,EAAG,QAAO;EAE9B,IAAM,IAAU;GACd;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACD,EAEK,KAAU,MAA2B;AACzC,OAAI,KAAU,KAA6B,QAAO;GAClD,IAAM,IAAM,OAAO,KAAU,WAAW,KAAK,UAAU,EAAM,GAAG,OAAO,EAAM;AAK7E,UAHI,WAAW,KAAK,EAAI,GACf,IAAI,EAAI,QAAQ,MAAM,OAAK,CAAC,KAE9B;KAGH,IAAW,CACf,EAAQ,KAAK,IAAI,EACjB,GAAG,EAAK,KAAK,MACX;GACE,EAAO,EAAI,GAAG;GACd,EAAO,EAAI,OAAO;GAClB,EAAO,EAAI,QAAQ;GACnB,EAAO,EAAI,UAAU;GACrB,EAAO,EAAI,SAAS;GACpB,EAAO,EAAI,WAAW;GACtB,EAAO,EAAI,SAAS;GACpB,EAAO,EAAI,UAAU;GACrB,EAAQ,EAAgC,UAAU;GAClD,EAAQ,EAA8B,QAAQ;GAC9C,EAAQ,EAA+B,SAAS;GAChD,EAAO,EAAI,UAAU;GACtB,CAAC,KAAK,IAAI,CACZ,CACF;AAED,SAAO,IAAI,KAAK,CAAC,EAAS,KAAK,KAAK,CAAC,EAAE,EAAE,MAAM,2BAA2B,CAAC;IAC1E,CAAC,EAAa,CAAC,EAEZ,IAAc,QAAkB;EACpC,IAAM,IAAO,GAAmB;AAChC,MAAI,CAAC,EAAM;EACX,IAAM,IAAM,IAAI,gBAAgB,EAAK,EAC/B,IAAO,SAAS,cAAc,IAAI,EAClC,qBAAQ,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI;AAM5D,EALA,EAAK,OAAO,GACZ,EAAK,aAAa,YAAY,cAAc,EAAM,MAAM,EACxD,SAAS,KAAK,YAAY,EAAK,EAC/B,EAAK,OAAO,EACZ,SAAS,KAAK,YAAY,EAAK,EAC/B,IAAI,gBAAgB,EAAI;IACvB,CAAC,EAAkB,CAAC;AAiEvB,QACE,kBAAC,GAAD;EACE,SAAS;EACT,WAAW;EACX,WAAU;EACV,aAAY;YAEZ,kBAAC,GAAD;GACE,OAAM;GACN,aAAa,sCAAsC,IAAQ,MAAM,EAAM,UAAU;GACjF,SACE,kBAAC,GAAD;IACE,OAAM;IACN,UAAU,KAAW,EAAa,WAAW;IAC7C,QAAO;IACP,mBAAkB;IAClB,QAAQ;KACN,MAAM;KACN,UAAU,+BAAc,IAAI,MAAM,EAAC,aAAa,CAAC,QAAQ,SAAS,IAAI,CAAC;KACvE,UAAU;KACV,SAAS,YAAY;MACnB,IAAM,IAAO,GAAmB;AAChC,aAAO,IAAO,EAAE,SAAM,GAAG;;KAE5B;IACD,YAAY;IACZ,CAAA;aAGJ,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,0EAA2E,CAAA,EAC7F,kBAAC,GAAD;QACE,aAAY;QACZ,OAAO;QACP,WAAW,MAAM,EAAU,EAAE,OAAO,MAAM;QAC1C,WAAU;QACV,CAAA,CACE;UACN,kBAAC,GAAD;OACE,aAAY;OACZ,OAAO;OACP,WAAW,MAAM;AAEf,QADA,EAAQ,EAAE,EACV,EAAkB,EAAE,OAAO,MAAM;;OAEnC,WAAU;OACV,CAAA,CACE;;KAEL,KACC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CAAwG,+BAC1E,EAAM,QAC9B;;KAGR,kBAAC,GAAD;MACW,SAzHH;OACd;QACE,KAAK;QACL,QAAQ;QACR,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBAAyB,EAAc,EAAI,OAAO;UAAQ,CAAA,EAC1E,kBAAC,QAAD;UAAM,WAAU;oBAAiD,EAAI;UAAc,CAAA,CAC/E;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAoB,EAAI;UAAc,CAAA,EACrD,kBAAC,OAAD;UAAK,WAAU;oBAA4C,EAAI;UAAgB,CAAA,CAC3E;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,OAAD;UAAK,WAAU;oBAAoB,EAAI;UAAe,CAAA,EACrD,EAAI,cACH,kBAAC,OAAD;UAAK,WAAU;oBAAf,CAAwD,KACpD,OAAO,EAAI,WAAW,CAAC,MAAM,GAAG,CAC9B;YAEJ;;QAET;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAI,WAAW,IAAI,EAAI,SAAS,MAAM,GAAG,KAAK;SAC1C,CAAA;QAEV;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MACP,kBAAC,QAAD;SAAM,WAAU;mBAA2C,EAAI,aAAa;SAAW,CAAA;QAE1F;OACD;QACE,KAAK;QACL,QAAQ;QACR,OAAO;QACP,SAAS,MAAoC,EAAmB,EAAI,UAAU;QAC/E;OACF;MA6DS,MAAM;MACN,eAAe,MAAM,EAAE;MACd;MACT,YACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,aAAY;OACZ,CAAA;MAEJ,CAAA;KAED,IAAQ,KACP,kBAAC,GAAD;MACQ;MACM;MACZ,YAAY;MACZ,UAAU;MACV,cAAc;MACd,CAAA;KAEA;;GACK,CAAA;EACG,CAAA"}
1
+ {"version":3,"file":"AuditLogsPage.js","names":[],"sources":["../../../src/admin/pages/AuditLogsPage.tsx"],"sourcesContent":["import { useState, useMemo, useCallback } from 'react';\nimport { FileText, Search, Download, User, Settings, Shield, Database, Key } from 'lucide-react';\nimport { SaveToFilesButton } from '@burdenoff/fe-libs/storage';\nimport {\n PageLayout,\n Input,\n DataTable,\n Pagination,\n EmptyState,\n Button,\n PermissionGuard,\n} from '../components';\nimport { useAdminPermissions } from '../hooks/useAdminPermissions';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useListAuditLogsQuery } from '../../generated/global-operations';\n\nconst PAGE_SIZE = 20;\n\nconst ACTION_ICONS: Record<string, React.ReactNode> = {\n auth: <User className=\"h-3 w-3\" />,\n user: <User className=\"h-3 w-3\" />,\n settings: <Settings className=\"h-3 w-3\" />,\n rbac: <Shield className=\"h-3 w-3\" />,\n data: <Database className=\"h-3 w-3\" />,\n api: <Key className=\"h-3 w-3\" />,\n};\n\nfunction getActionIcon(action: string) {\n const prefix = action.split('.')[0];\n return ACTION_ICONS[prefix] ?? <FileText className=\"h-3 w-3\" />;\n}\n\n/**\n * Audit logs page — connected to real backend\n */\nexport function AuditLogsPage() {\n const { canViewAuditLogs, isLoading: permissionsLoading } = useAdminPermissions();\n\n const [page, setPage] = useState(1);\n const [search, setSearch] = useState('');\n const [resourceFilter, setResourceFilter] = useState('');\n\n const skipQueries = permissionsLoading || !canViewAuditLogs;\n\n const { data, loading, error } = useListAuditLogsQuery({\n variables: {\n resource: resourceFilter || undefined,\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n fetchPolicy: 'cache-and-network',\n skip: skipQueries,\n });\n\n const auditLogs = useMemo(() => {\n const edges = data?.auditLogs?.edges ?? [];\n return edges.map((e: (typeof edges)[number]) => e.node);\n }, [data]);\n\n type AuditLogNode = (typeof auditLogs)[number];\n\n const filteredLogs = useMemo(() => {\n if (!search) return auditLogs;\n const q = search.toLowerCase();\n return auditLogs.filter(\n (log: AuditLogNode) =>\n log.action.toLowerCase().includes(q) ||\n log.actorId.toLowerCase().includes(q) ||\n log.resource.toLowerCase().includes(q) ||\n log.tenantId.toLowerCase().includes(q)\n );\n }, [auditLogs, search]);\n\n const total = data?.auditLogs?.totalCount ?? 0;\n const totalPages = Math.ceil(total / PAGE_SIZE);\n\n const buildAuditCsvBlob = useCallback((): Blob | null => {\n const rows = filteredLogs;\n if (rows.length === 0) return null;\n\n const headers = [\n 'id',\n 'action',\n 'actorId',\n 'actorType',\n 'resource',\n 'resourceId',\n 'tenantId',\n 'ipAddress',\n 'userAgent',\n 'changes',\n 'metadata',\n 'createdAt',\n ];\n\n const escape = (value: unknown): string => {\n if (value === null || value === undefined) return '';\n const str = typeof value === 'object' ? JSON.stringify(value) : String(value);\n // RFC 4180: wrap in quotes if it contains quote, comma, or newline; double internal quotes\n if (/[\",\\n\\r]/.test(str)) {\n return `\"${str.replace(/\"/g, '\"\"')}\"`;\n }\n return str;\n };\n\n const csvLines = [\n headers.join(','),\n ...rows.map((log: AuditLogNode) =>\n [\n escape(log.id),\n escape(log.action),\n escape(log.actorId),\n escape(log.actorType),\n escape(log.resource),\n escape(log.resourceId),\n escape(log.tenantId),\n escape(log.ipAddress),\n escape((log as { userAgent?: unknown }).userAgent),\n escape((log as { changes?: unknown }).changes),\n escape((log as { metadata?: unknown }).metadata),\n escape(log.createdAt),\n ].join(',')\n ),\n ];\n\n return new Blob([csvLines.join('\\n')], { type: 'text/csv;charset=utf-8;' });\n }, [filteredLogs]);\n\n const exportToCsv = useCallback(() => {\n const blob = buildAuditCsvBlob();\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const link = document.createElement('a');\n const stamp = new Date().toISOString().replace(/[:.]/g, '-');\n link.href = url;\n link.setAttribute('download', `audit-logs-${stamp}.csv`);\n document.body.appendChild(link);\n link.click();\n document.body.removeChild(link);\n URL.revokeObjectURL(url);\n }, [buildAuditCsvBlob]);\n\n const columns = [\n {\n key: 'action',\n header: 'Action',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"flex items-center gap-2\">\n <span className=\"text-text-secondary\">{getActionIcon(log.action)}</span>\n <span className=\"font-mono text-sm font-medium text-text-primary\">{log.action}</span>\n </div>\n ),\n },\n {\n key: 'actorId',\n header: 'Actor',\n width: '180px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.actorId}</div>\n <div className=\"text-xs text-text-secondary capitalize\">{log.actorType}</div>\n </div>\n ),\n },\n {\n key: 'resource',\n header: 'Resource',\n width: '150px',\n render: (log: (typeof auditLogs)[number]) => (\n <div className=\"min-w-0\">\n <div className=\"truncate text-sm\">{log.resource}</div>\n {log.resourceId && (\n <div className=\"truncate text-xs text-text-secondary\">\n #{String(log.resourceId).slice(-8)}\n </div>\n )}\n </div>\n ),\n },\n {\n key: 'tenantId',\n header: 'Tenant',\n width: '130px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"text-xs text-text-secondary\">\n {log.tenantId ? `#${log.tenantId.slice(-8)}` : '—'}\n </span>\n ),\n },\n {\n key: 'ipAddress',\n header: 'IP',\n width: '120px',\n render: (log: (typeof auditLogs)[number]) => (\n <span className=\"font-mono text-xs text-text-secondary\">{log.ipAddress ?? '—'}</span>\n ),\n },\n {\n key: 'createdAt',\n header: 'Time',\n width: '110px',\n render: (log: (typeof auditLogs)[number]) => formatRelativeTime(log.createdAt),\n },\n ];\n\n return (\n <PermissionGuard\n allowed={canViewAuditLogs}\n isLoading={permissionsLoading}\n pageTitle=\"Audit Logs\"\n denyMessage=\"You don't have permission to view audit logs.\"\n >\n <PageLayout\n title=\"Audit Logs\"\n description={`Security and compliance audit trail${total ? ` · ${total} total` : ''}`}\n actions={\n <SaveToFilesButton\n label=\"Export CSV\"\n disabled={loading || filteredLogs.length === 0}\n testId=\"audit-logs-export\"\n defaultFolderPath=\"/Admin/Audit Logs\"\n source={{\n kind: 'blob',\n filename: `audit-logs-${new Date().toISOString().replace(/[:.]/g, '-')}.csv`,\n mimeType: 'text/csv',\n getBlob: async () => {\n const blob = buildAuditCsvBlob();\n return blob ? { blob } : null;\n },\n }}\n onDownload={exportToCsv}\n />\n }\n >\n <div className=\"space-y-4\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"relative max-w-sm flex-1\">\n <Search className=\"absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n placeholder=\"Search by action, actor, or resource...\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n <Input\n placeholder=\"Filter by resource (e.g. tenant, user)...\"\n value={resourceFilter}\n onChange={(e) => {\n setPage(1);\n setResourceFilter(e.target.value);\n }}\n className=\"max-w-xs\"\n />\n </div>\n\n {error && (\n <div className=\"rounded-md border border-status-error-border/50 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n Failed to load audit logs: {error.message}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={filteredLogs}\n keyExtractor={(l) => l.id}\n loading={loading}\n emptyState={\n <EmptyState\n icon={<FileText className=\"h-8 w-8\" />}\n title=\"No audit logs found\"\n description=\"Audit events will appear here when actions are performed\"\n />\n }\n />\n\n {total > PAGE_SIZE && (\n <Pagination\n page={page}\n totalPages={totalPages}\n totalItems={total}\n pageSize={PAGE_SIZE}\n onPageChange={setPage}\n />\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n}\n"],"mappings":"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"}