@burdenoff/microfe-admin 2026.624.1 → 2026.626.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 (76) hide show
  1. package/dist/admin/components/AccessDenied.js +2 -2
  2. package/dist/admin/components/AccessDenied.js.map +1 -1
  3. package/dist/admin/components/ActivityFeed.js +4 -4
  4. package/dist/admin/components/ActivityFeed.js.map +1 -1
  5. package/dist/admin/components/Charts.js.map +1 -1
  6. package/dist/admin/components/ConfirmDialog.js +1 -1
  7. package/dist/admin/components/ConfirmDialog.js.map +1 -1
  8. package/dist/admin/components/DataTable.js +5 -5
  9. package/dist/admin/components/DataTable.js.map +1 -1
  10. package/dist/admin/components/FormSheet.js +1 -1
  11. package/dist/admin/components/FormSheet.js.map +1 -1
  12. package/dist/admin/components/MetricCard.js +40 -37
  13. package/dist/admin/components/MetricCard.js.map +1 -1
  14. package/dist/admin/components/PageLayout.js +28 -22
  15. package/dist/admin/components/PageLayout.js.map +1 -1
  16. package/dist/admin/components/TenantSsoPanel.js +1 -1
  17. package/dist/admin/components/TenantSsoPanel.js.map +1 -1
  18. package/dist/admin/components/Toast.js +11 -11
  19. package/dist/admin/components/Toast.js.map +1 -1
  20. package/dist/admin/components/forms/SubscriptionForm.js +2 -2
  21. package/dist/admin/components/forms/SubscriptionForm.js.map +1 -1
  22. package/dist/admin/components/forms/TenantForm.js +1 -1
  23. package/dist/admin/components/forms/UserForm.js +1 -1
  24. package/dist/admin/components/ui.js +48 -41
  25. package/dist/admin/components/ui.js.map +1 -1
  26. package/dist/admin/constants/index.js +33 -24
  27. package/dist/admin/constants/index.js.map +1 -1
  28. package/dist/admin/pages/AuditLogsPage.js +94 -77
  29. package/dist/admin/pages/AuditLogsPage.js.map +1 -1
  30. package/dist/admin/pages/BillingPage.js +70 -18
  31. package/dist/admin/pages/BillingPage.js.map +1 -1
  32. package/dist/admin/pages/CommunicationsPage.js +98 -90
  33. package/dist/admin/pages/CommunicationsPage.js.map +1 -1
  34. package/dist/admin/pages/ContentPagesPage.js +276 -259
  35. package/dist/admin/pages/ContentPagesPage.js.map +1 -1
  36. package/dist/admin/pages/DashboardPage.js +114 -82
  37. package/dist/admin/pages/DashboardPage.js.map +1 -1
  38. package/dist/admin/pages/FeatureFlagsPage.js +94 -74
  39. package/dist/admin/pages/FeatureFlagsPage.js.map +1 -1
  40. package/dist/admin/pages/OrganizationsListPage.js +106 -86
  41. package/dist/admin/pages/OrganizationsListPage.js.map +1 -1
  42. package/dist/admin/pages/ProductsPage.js +139 -128
  43. package/dist/admin/pages/ProductsPage.js.map +1 -1
  44. package/dist/admin/pages/ReferralsPage.js +74 -52
  45. package/dist/admin/pages/ReferralsPage.js.map +1 -1
  46. package/dist/admin/pages/RolesPermissionsPage.js +137 -116
  47. package/dist/admin/pages/RolesPermissionsPage.js.map +1 -1
  48. package/dist/admin/pages/SREPage.js +38 -32
  49. package/dist/admin/pages/SREPage.js.map +1 -1
  50. package/dist/admin/pages/SandboxSettingsPage.js +361 -322
  51. package/dist/admin/pages/SandboxSettingsPage.js.map +1 -1
  52. package/dist/admin/pages/SettingsPage.js +145 -130
  53. package/dist/admin/pages/SettingsPage.js.map +1 -1
  54. package/dist/admin/pages/SyncFailuresPage.js +123 -118
  55. package/dist/admin/pages/SyncFailuresPage.js.map +1 -1
  56. package/dist/admin/pages/TenantDetailPage.js +261 -218
  57. package/dist/admin/pages/TenantDetailPage.js.map +1 -1
  58. package/dist/admin/pages/TenantFormRoutePage.js +65 -61
  59. package/dist/admin/pages/TenantFormRoutePage.js.map +1 -1
  60. package/dist/admin/pages/TenantsListPage.js +88 -71
  61. package/dist/admin/pages/TenantsListPage.js.map +1 -1
  62. package/dist/admin/pages/UsersListPage.js +129 -112
  63. package/dist/admin/pages/UsersListPage.js.map +1 -1
  64. package/dist/admin/pages/WaitlistPage.js +80 -63
  65. package/dist/admin/pages/WaitlistPage.js.map +1 -1
  66. package/dist/admin/pages/WorkflowsPage.js +108 -100
  67. package/dist/admin/pages/WorkflowsPage.js.map +1 -1
  68. package/dist/admin/pages/WorkspacesListPage.js +62 -49
  69. package/dist/admin/pages/WorkspacesListPage.js.map +1 -1
  70. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js +2 -2
  71. package/dist/node_modules/@hookform/resolvers/dist/resolvers.js.map +1 -1
  72. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js +35 -26
  73. package/dist/node_modules/@hookform/resolvers/zod/dist/zod.js.map +1 -1
  74. package/dist/node_modules/react-hook-form/dist/index.esm.js +609 -516
  75. package/dist/node_modules/react-hook-form/dist/index.esm.js.map +1 -1
  76. package/package.json +2 -2
@@ -8,8 +8,8 @@ var r = ({ message: r = "You don't have permission to access this page." }) => /
8
8
  className: "text-center space-y-3",
9
9
  children: [
10
10
  /* @__PURE__ */ t("div", {
11
- className: "w-14 h-14 mx-auto rounded-full bg-red-50 dark:bg-red-950/30 flex items-center justify-center",
12
- children: /* @__PURE__ */ t(e, { className: "w-7 h-7 text-red-500" })
11
+ className: "w-14 h-14 mx-auto rounded-full bg-status-error-bg border border-status-error-border flex items-center justify-center",
12
+ children: /* @__PURE__ */ t(e, { className: "w-7 h-7 text-status-error-text" })
13
13
  }),
14
14
  /* @__PURE__ */ t("h2", {
15
15
  className: "text-lg font-semibold text-text-primary",
@@ -1 +1 @@
1
- {"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../src/admin/components/AccessDenied.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { ShieldOff } from 'lucide-react';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({\n message = \"You don't have permission to access this page.\",\n}) => {\n return (\n <div role=\"alert\" className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-3\">\n <div className=\"w-14 h-14 mx-auto rounded-full bg-red-50 dark:bg-red-950/30 flex items-center justify-center\">\n <ShieldOff className=\"w-7 h-7 text-red-500\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">{message}</p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAOA,IAAa,KAAuC,EAClD,aAAU,uDAGR,kBAAC,OAAD;CAAK,MAAK;CAAQ,WAAU;WAC1B,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAW,WAAU,wBAAyB,CAAA;IAC1C,CAAA;GACN,kBAAC,MAAD;IAAI,WAAU;cAA0C;IAAkB,CAAA;GAC1E,kBAAC,KAAD;IAAG,WAAU;cAAwC;IAAY,CAAA;GAC7D;;CACF,CAAA"}
1
+ {"version":3,"file":"AccessDenied.js","names":[],"sources":["../../../src/admin/components/AccessDenied.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { ShieldOff } from 'lucide-react';\n\ninterface AccessDeniedProps {\n message?: string;\n}\n\nexport const AccessDenied: FC<AccessDeniedProps> = ({\n message = \"You don't have permission to access this page.\",\n}) => {\n return (\n <div role=\"alert\" className=\"flex items-center justify-center h-full min-h-[400px]\">\n <div className=\"text-center space-y-3\">\n <div className=\"w-14 h-14 mx-auto rounded-full bg-status-error-bg border border-status-error-border flex items-center justify-center\">\n <ShieldOff className=\"w-7 h-7 text-status-error-text\" />\n </div>\n <h2 className=\"text-lg font-semibold text-text-primary\">Access Denied</h2>\n <p className=\"text-sm text-text-secondary max-w-sm\">{message}</p>\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAOA,IAAa,KAAuC,EAClD,aAAU,uDAGR,kBAAC,OAAD;CAAK,MAAK;CAAQ,WAAU;WAC1B,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAW,WAAU,kCAAmC,CAAA;IACpD,CAAA;GACN,kBAAC,MAAD;IAAI,WAAU;cAA0C;IAAkB,CAAA;GAC1E,kBAAC,KAAD;IAAG,WAAU;cAAwC;IAAY,CAAA;GAC7D;;CACF,CAAA"}
@@ -37,10 +37,10 @@ function C({ activity: t }) {
37
37
  className: "flex items-start gap-3",
38
38
  children: [/* @__PURE__ */ b("div", {
39
39
  className: e("rounded-full p-2", {
40
- info: "bg-info/10 text-info",
41
- warning: "bg-warning/10 text-warning",
42
- error: "bg-status-error-bg/10 text-status-error-text",
43
- success: "bg-success/10 text-success"
40
+ info: "bg-status-info-bg text-status-info-text",
41
+ warning: "bg-status-warning-bg text-status-warning-text",
42
+ error: "bg-status-error-bg text-status-error-text",
43
+ success: "bg-status-success-bg text-status-success-text"
44
44
  }[t.severity || "info"]),
45
45
  children: n
46
46
  }), /* @__PURE__ */ x("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityFeed.js","names":[],"sources":["../../../src/admin/components/ActivityFeed.tsx"],"sourcesContent":["import { Link } from 'react-router-dom';\nimport {\n Plus,\n Ban,\n AlertTriangle,\n CheckCircle,\n UserPlus,\n Rocket,\n XCircle,\n Activity,\n CreditCard,\n Mail,\n Settings,\n Shield,\n} from 'lucide-react';\nimport { Card, CardHeader, CardTitle, CardContent, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { Activity as ActivityType } from '../types';\n\ninterface ActivityFeedProps {\n /** List of activities */\n activities: ActivityType[];\n /** Maximum items to display */\n maxItems?: number;\n /** Loading state */\n loading?: boolean;\n /** Link to view all activities */\n viewAllLink?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Activity feed component showing recent platform activities\n */\nexport function ActivityFeed({\n activities,\n maxItems = 10,\n loading,\n viewAllLink = '/activity',\n className,\n}: ActivityFeedProps) {\n const displayActivities = activities.slice(0, maxItems);\n\n return (\n <Card className={className}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-lg\">Recent Activity</CardTitle>\n <Link to={viewAllLink} className=\"text-sm text-primary hover:underline\">\n View All\n </Link>\n </CardHeader>\n <CardContent>\n {loading ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 5 }).map((_, i) => (\n <ActivityItemSkeleton key={i} />\n ))}\n </div>\n ) : displayActivities.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-text-secondary\">No recent activity</p>\n ) : (\n <div className=\"space-y-4\">\n {displayActivities.map((activity) => (\n <ActivityItem key={activity.id} activity={activity} />\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\ninterface ActivityItemProps {\n activity: ActivityType;\n}\n\nfunction ActivityItem({ activity }: ActivityItemProps) {\n const icon = getActivityIcon(activity.type);\n const severityColors = {\n info: 'bg-info/10 text-info',\n warning: 'bg-warning/10 text-warning',\n error: 'bg-status-error-bg/10 text-status-error-text',\n success: 'bg-success/10 text-success',\n };\n const severity = activity.severity || 'info';\n\n return (\n <div className=\"flex items-start gap-3\">\n <div className={cn('rounded-full p-2', severityColors[severity])}>{icon}</div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{activity.description}</p>\n <div className=\"mt-1 flex items-center gap-2 text-xs text-text-secondary\">\n <span>{activity.actor}</span>\n <span>-</span>\n <span>{formatRelativeTime(activity.timestamp)}</span>\n </div>\n </div>\n </div>\n );\n}\n\nfunction ActivityItemSkeleton() {\n return (\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-9 w-9 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n );\n}\n\nfunction getActivityIcon(type: string) {\n const iconMap: Record<string, React.ReactNode> = {\n 'tenant.created': <Plus className=\"h-4 w-4\" />,\n 'tenant.suspended': <Ban className=\"h-4 w-4\" />,\n 'tenant.activated': <CheckCircle className=\"h-4 w-4\" />,\n 'payment.failed': <AlertTriangle className=\"h-4 w-4\" />,\n 'payment.succeeded': <CreditCard className=\"h-4 w-4\" />,\n 'user.created': <UserPlus className=\"h-4 w-4\" />,\n 'user.blocked': <Ban className=\"h-4 w-4\" />,\n 'deployment.started': <Rocket className=\"h-4 w-4\" />,\n 'deployment.completed': <CheckCircle className=\"h-4 w-4\" />,\n 'deployment.failed': <XCircle className=\"h-4 w-4\" />,\n 'newsletter.sent': <Mail className=\"h-4 w-4\" />,\n 'settings.changed': <Settings className=\"h-4 w-4\" />,\n 'security.alert': <Shield className=\"h-4 w-4\" />,\n };\n\n return iconMap[type] || <Activity className=\"h-4 w-4\" />;\n}\n"],"mappings":";;;;;;;AAoCA,SAAgB,EAAa,EAC3B,eACA,cAAW,IACX,YACA,iBAAc,aACd,gBACoB;CACpB,IAAM,IAAoB,EAAW,MAAM,GAAG,EAAS;AAEvD,QACE,kBAAC,GAAD;EAAiB;YAAjB,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAAU;IAA2B,CAAA,EAC1D,kBAAC,GAAD;IAAM,IAAI;IAAa,WAAU;cAAuC;IAEjE,CAAA,CACI;MACb,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,OAAD;GAAK,WAAU;aACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,GAAD,EAAgC,EAAL,EAAK,CAChC;GACE,CAAA,GACJ,EAAkB,WAAW,IAC/B,kBAAC,KAAD;GAAG,WAAU;aAA+C;GAAsB,CAAA,GAElF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAkB,KAAK,MACtB,kBAAC,GAAD,EAA0C,aAAY,EAAnC,EAAS,GAA0B,CACtD;GACE,CAAA,EAEI,CAAA,CACT;;;AAQX,SAAS,EAAa,EAAE,eAA+B;CACrD,IAAM,IAAO,EAAgB,EAAS,KAAK;AAS3C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAW,EAAG,oBAVA;IACrB,MAAM;IACN,SAAS;IACT,OAAO;IACP,SAAS;IACV,CACgB,EAAS,YAAY,QAI8B;aAAG;GAAW,CAAA,EAC9E,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAyC,EAAS;IAAgB,CAAA,EAC/E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD,EAAA,UAAO,EAAS,OAAa,CAAA;KAC7B,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA;KACd,kBAAC,QAAD,EAAA,UAAO,EAAmB,EAAS,UAAU,EAAQ,CAAA;KACjD;MACF;KACF;;;AAIV,SAAS,IAAuB;AAC9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA,EAC7C,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,EAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;KACF;;;AAIV,SAAS,EAAgB,GAAc;AAiBrC,QAhBiD;EAC/C,kBAAkB,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;EAC9C,oBAAoB,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;EAC/C,oBAAoB,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;EACvD,kBAAkB,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;EACvD,qBAAqB,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;EACvD,gBAAgB,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EAChD,gBAAgB,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;EAC3C,sBAAsB,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACpD,wBAAwB,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;EAC3D,qBAAqB,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;EACpD,mBAAmB,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;EAC/C,oBAAoB,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EACpD,kBAAkB,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACjD,CAEc,MAAS,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA"}
1
+ {"version":3,"file":"ActivityFeed.js","names":[],"sources":["../../../src/admin/components/ActivityFeed.tsx"],"sourcesContent":["import { Link } from 'react-router-dom';\nimport {\n Plus,\n Ban,\n AlertTriangle,\n CheckCircle,\n UserPlus,\n Rocket,\n XCircle,\n Activity,\n CreditCard,\n Mail,\n Settings,\n Shield,\n} from 'lucide-react';\nimport { Card, CardHeader, CardTitle, CardContent, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatRelativeTime } from '../utils/formatters';\nimport type { Activity as ActivityType } from '../types';\n\ninterface ActivityFeedProps {\n /** List of activities */\n activities: ActivityType[];\n /** Maximum items to display */\n maxItems?: number;\n /** Loading state */\n loading?: boolean;\n /** Link to view all activities */\n viewAllLink?: string;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Activity feed component showing recent platform activities\n */\nexport function ActivityFeed({\n activities,\n maxItems = 10,\n loading,\n viewAllLink = '/activity',\n className,\n}: ActivityFeedProps) {\n const displayActivities = activities.slice(0, maxItems);\n\n return (\n <Card className={className}>\n <CardHeader className=\"flex flex-row items-center justify-between\">\n <CardTitle className=\"text-lg\">Recent Activity</CardTitle>\n <Link to={viewAllLink} className=\"text-sm text-primary hover:underline\">\n View All\n </Link>\n </CardHeader>\n <CardContent>\n {loading ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 5 }).map((_, i) => (\n <ActivityItemSkeleton key={i} />\n ))}\n </div>\n ) : displayActivities.length === 0 ? (\n <p className=\"py-8 text-center text-sm text-text-secondary\">No recent activity</p>\n ) : (\n <div className=\"space-y-4\">\n {displayActivities.map((activity) => (\n <ActivityItem key={activity.id} activity={activity} />\n ))}\n </div>\n )}\n </CardContent>\n </Card>\n );\n}\n\ninterface ActivityItemProps {\n activity: ActivityType;\n}\n\nfunction ActivityItem({ activity }: ActivityItemProps) {\n const icon = getActivityIcon(activity.type);\n const severityColors = {\n info: 'bg-status-info-bg text-status-info-text',\n warning: 'bg-status-warning-bg text-status-warning-text',\n error: 'bg-status-error-bg text-status-error-text',\n success: 'bg-status-success-bg text-status-success-text',\n };\n const severity = activity.severity || 'info';\n\n return (\n <div className=\"flex items-start gap-3\">\n <div className={cn('rounded-full p-2', severityColors[severity])}>{icon}</div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"text-sm font-medium text-text-primary\">{activity.description}</p>\n <div className=\"mt-1 flex items-center gap-2 text-xs text-text-secondary\">\n <span>{activity.actor}</span>\n <span>-</span>\n <span>{formatRelativeTime(activity.timestamp)}</span>\n </div>\n </div>\n </div>\n );\n}\n\nfunction ActivityItemSkeleton() {\n return (\n <div className=\"flex items-start gap-3\">\n <Skeleton className=\"h-9 w-9 rounded-full\" />\n <div className=\"flex-1 space-y-2\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n );\n}\n\nfunction getActivityIcon(type: string) {\n const iconMap: Record<string, React.ReactNode> = {\n 'tenant.created': <Plus className=\"h-4 w-4\" />,\n 'tenant.suspended': <Ban className=\"h-4 w-4\" />,\n 'tenant.activated': <CheckCircle className=\"h-4 w-4\" />,\n 'payment.failed': <AlertTriangle className=\"h-4 w-4\" />,\n 'payment.succeeded': <CreditCard className=\"h-4 w-4\" />,\n 'user.created': <UserPlus className=\"h-4 w-4\" />,\n 'user.blocked': <Ban className=\"h-4 w-4\" />,\n 'deployment.started': <Rocket className=\"h-4 w-4\" />,\n 'deployment.completed': <CheckCircle className=\"h-4 w-4\" />,\n 'deployment.failed': <XCircle className=\"h-4 w-4\" />,\n 'newsletter.sent': <Mail className=\"h-4 w-4\" />,\n 'settings.changed': <Settings className=\"h-4 w-4\" />,\n 'security.alert': <Shield className=\"h-4 w-4\" />,\n };\n\n return iconMap[type] || <Activity className=\"h-4 w-4\" />;\n}\n"],"mappings":";;;;;;;AAoCA,SAAgB,EAAa,EAC3B,eACA,cAAW,IACX,YACA,iBAAc,aACd,gBACoB;CACpB,IAAM,IAAoB,EAAW,MAAM,GAAG,EAAS;AAEvD,QACE,kBAAC,GAAD;EAAiB;YAAjB,CACE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAAU;IAA2B,CAAA,EAC1D,kBAAC,GAAD;IAAM,IAAI;IAAa,WAAU;cAAuC;IAEjE,CAAA,CACI;MACb,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,OAAD;GAAK,WAAU;aACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,GAAD,EAAgC,EAAL,EAAK,CAChC;GACE,CAAA,GACJ,EAAkB,WAAW,IAC/B,kBAAC,KAAD;GAAG,WAAU;aAA+C;GAAsB,CAAA,GAElF,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAkB,KAAK,MACtB,kBAAC,GAAD,EAA0C,aAAY,EAAnC,EAAS,GAA0B,CACtD;GACE,CAAA,EAEI,CAAA,CACT;;;AAQX,SAAS,EAAa,EAAE,eAA+B;CACrD,IAAM,IAAO,EAAgB,EAAS,KAAK;AAS3C,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAW,EAAG,oBAVA;IACrB,MAAM;IACN,SAAS;IACT,OAAO;IACP,SAAS;IACV,CACgB,EAAS,YAAY,QAI8B;aAAG;GAAW,CAAA,EAC9E,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cAAyC,EAAS;IAAgB,CAAA,EAC/E,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,QAAD,EAAA,UAAO,EAAS,OAAa,CAAA;KAC7B,kBAAC,QAAD,EAAA,UAAM,KAAQ,CAAA;KACd,kBAAC,QAAD,EAAA,UAAO,EAAmB,EAAS,UAAU,EAAQ,CAAA;KACjD;MACF;KACF;;;AAIV,SAAS,IAAuB;AAC9B,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAU,WAAU,wBAAyB,CAAA,EAC7C,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,EAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;KACF;;;AAIV,SAAS,EAAgB,GAAc;AAiBrC,QAhBiD;EAC/C,kBAAkB,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;EAC9C,oBAAoB,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;EAC/C,oBAAoB,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;EACvD,kBAAkB,kBAAC,GAAD,EAAe,WAAU,WAAY,CAAA;EACvD,qBAAqB,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;EACvD,gBAAgB,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EAChD,gBAAgB,kBAAC,GAAD,EAAK,WAAU,WAAY,CAAA;EAC3C,sBAAsB,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACpD,wBAAwB,kBAAC,GAAD,EAAa,WAAU,WAAY,CAAA;EAC3D,qBAAqB,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;EACpD,mBAAmB,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;EAC/C,oBAAoB,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;EACpD,kBAAkB,kBAAC,GAAD,EAAQ,WAAU,WAAY,CAAA;EACjD,CAEc,MAAS,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"Charts.js","names":[],"sources":["../../../src/admin/components/Charts.tsx"],"sourcesContent":["import {\n LineChart,\n Line,\n AreaChart,\n Area,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n Legend,\n ResponsiveContainer,\n} from 'recharts';\nimport { Card, CardHeader, CardTitle, CardContent, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatNumber } from '../utils/formatters';\nimport type { RevenueTrendPoint, TenantGrowthPoint, ProductUsagePoint } from '../types';\n\n// Chart colors using CSS variables for theme support\nconst CHART_COLORS = [\n 'hsl(var(--chart-1, 221.2 83.2% 53.3%))',\n 'hsl(var(--chart-2, 142.1 76.2% 36.3%))',\n 'hsl(var(--chart-3, 47.9 95.8% 53.1%))',\n 'hsl(var(--chart-4, 0 84.2% 60.2%))',\n 'hsl(var(--chart-5, 262.1 83.3% 57.8%))',\n];\n\n// ============================================================================\n// Revenue Trend Chart (Line Chart)\n// ============================================================================\n\ninterface RevenueTrendChartProps {\n data: RevenueTrendPoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function RevenueTrendChart({ data, loading, className }: RevenueTrendChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Revenue Trend</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[300px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={300}>\n <LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"date\"\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n />\n <YAxis\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n tickFormatter={(value) => formatCurrency(value)}\n />\n <Tooltip\n contentStyle={{\n backgroundColor: 'hsl(var(--card))',\n border: '1px solid hsl(var(--border))',\n borderRadius: '6px',\n }}\n labelStyle={{ color: 'hsl(var(--foreground))' }}\n formatter={(value: number) => [formatCurrency(value), '']}\n />\n <Legend />\n <Line\n type=\"monotone\"\n dataKey=\"mrr\"\n name=\"MRR\"\n stroke={CHART_COLORS[0]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 6 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"arr\"\n name=\"ARR\"\n stroke={CHART_COLORS[1]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 6 }}\n />\n </LineChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Tenant Growth Chart (Area Chart)\n// ============================================================================\n\ninterface TenantGrowthChartProps {\n data: TenantGrowthPoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function TenantGrowthChart({ data, loading, className }: TenantGrowthChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Tenant Growth</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[250px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={250}>\n <AreaChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"date\"\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n />\n <YAxis className=\"text-xs fill-muted-foreground\" tick={{ fill: 'currentColor' }} />\n <Tooltip\n contentStyle={{\n backgroundColor: 'hsl(var(--card))',\n border: '1px solid hsl(var(--border))',\n borderRadius: '6px',\n }}\n labelStyle={{ color: 'hsl(var(--foreground))' }}\n />\n <Legend />\n <Area\n type=\"monotone\"\n dataKey=\"new\"\n name=\"New\"\n stackId=\"1\"\n stroke={CHART_COLORS[0]}\n fill={CHART_COLORS[0]}\n fillOpacity={0.6}\n />\n <Area\n type=\"monotone\"\n dataKey=\"active\"\n name=\"Active\"\n stackId=\"2\"\n stroke={CHART_COLORS[1]}\n fill={CHART_COLORS[1]}\n fillOpacity={0.6}\n />\n <Area\n type=\"monotone\"\n dataKey=\"churned\"\n name=\"Churned\"\n stackId=\"3\"\n stroke={CHART_COLORS[3]}\n fill={CHART_COLORS[3]}\n fillOpacity={0.6}\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Product Usage Chart (Pie Chart)\n// ============================================================================\n\ninterface ProductUsageChartProps {\n data: ProductUsagePoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function ProductUsageChart({ data, loading, className }: ProductUsageChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Product Usage</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[250px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={250}>\n <PieChart>\n <Pie\n data={data}\n dataKey=\"tenants\"\n nameKey=\"product\"\n cx=\"50%\"\n cy=\"50%\"\n outerRadius={80}\n label={({ name, percent }: { name: string; percent: number }) =>\n `${name} (${(percent * 100).toFixed(0)}%)`\n }\n labelLine={false}\n >\n {data.map((entry, index) => (\n <Cell\n key={`cell-${index}`}\n fill={entry.color || CHART_COLORS[index % CHART_COLORS.length]}\n />\n ))}\n </Pie>\n <Tooltip\n contentStyle={{\n backgroundColor: 'hsl(var(--card))',\n border: '1px solid hsl(var(--border))',\n borderRadius: '6px',\n }}\n formatter={(value: number) => [formatNumber(value) + ' tenants', '']}\n />\n <Legend />\n </PieChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Quick Links Card\n// ============================================================================\n\ninterface QuickLink {\n to: string;\n icon: React.ReactNode;\n label: string;\n description?: string;\n}\n\ninterface QuickLinksProps {\n links: QuickLink[];\n className?: string;\n}\n\nexport function QuickLinks({ links, className }: QuickLinksProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Quick Actions</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {links.map((link) => (\n <a\n key={link.to}\n href={link.to}\n className={cn(\n 'flex items-center gap-3 rounded-lg border border-border-subtle p-3',\n 'transition-colors hover:bg-accent'\n )}\n >\n <div className=\"rounded-md bg-action-primary-bg/10 p-2 text-primary\">{link.icon}</div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{link.label}</p>\n {link.description && (\n <p className=\"truncate text-xs text-text-secondary\">{link.description}</p>\n )}\n </div>\n </a>\n ))}\n </div>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":";;;;;AAqPA,SAAgB,EAAW,EAAE,UAAO,gBAA8B;AAChE,QACE,kBAAC,GAAD;EAAiB;YAAjB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAW,WAAU;aAAU;GAAyB,CAAA,EAC7C,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,KAAD;IAEE,MAAM,EAAK;IACX,WAAW,EACT,sEACA,oCACD;cANH,CAQE,kBAAC,OAAD;KAAK,WAAU;eAAuD,EAAK;KAAW,CAAA,EACtF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAiC,EAAK;MAAU,CAAA,EAC5D,EAAK,eACJ,kBAAC,KAAD;MAAG,WAAU;gBAAwC,EAAK;MAAgB,CAAA,CAExE;OACJ;MAdG,EAAK,GAcR,CACJ;GACE,CAAA,EACM,CAAA,CACT"}
1
+ {"version":3,"file":"Charts.js","names":[],"sources":["../../../src/admin/components/Charts.tsx"],"sourcesContent":["import {\n LineChart,\n Line,\n AreaChart,\n Area,\n PieChart,\n Pie,\n Cell,\n XAxis,\n YAxis,\n CartesianGrid,\n Tooltip,\n Legend,\n ResponsiveContainer,\n} from 'recharts';\nimport { Card, CardHeader, CardTitle, CardContent, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatCurrency, formatNumber } from '../utils/formatters';\nimport type { RevenueTrendPoint, TenantGrowthPoint, ProductUsagePoint } from '../types';\n\n// Chart colors using semantic chart tokens (theme-aware; resolve correctly in\n// every theme + dark mode — no hardcoded hsl() fallbacks).\nconst CHART_COLORS = [\n 'var(--color-chart-1)',\n 'var(--color-chart-2)',\n 'var(--color-chart-3)',\n 'var(--color-chart-4)',\n 'var(--color-chart-5)',\n];\n\n// Shared tooltip styling resolved from semantic surface tokens so popovers stay\n// legible (strong contrast) in dark mode rather than rendering invalid colors.\nconst CHART_TOOLTIP_CONTENT_STYLE = {\n backgroundColor: 'var(--color-bg-elevated)',\n border: '1px solid var(--color-border-seam)',\n borderRadius: '6px',\n} as const;\n\nconst CHART_TOOLTIP_LABEL_STYLE = { color: 'var(--color-text-primary)' } as const;\n\n// ============================================================================\n// Revenue Trend Chart (Line Chart)\n// ============================================================================\n\ninterface RevenueTrendChartProps {\n data: RevenueTrendPoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function RevenueTrendChart({ data, loading, className }: RevenueTrendChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Revenue Trend</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[300px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={300}>\n <LineChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"date\"\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n />\n <YAxis\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n tickFormatter={(value) => formatCurrency(value)}\n />\n <Tooltip\n contentStyle={CHART_TOOLTIP_CONTENT_STYLE}\n labelStyle={CHART_TOOLTIP_LABEL_STYLE}\n formatter={(value: number) => [formatCurrency(value), '']}\n />\n <Legend />\n <Line\n type=\"monotone\"\n dataKey=\"mrr\"\n name=\"MRR\"\n stroke={CHART_COLORS[0]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 6 }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"arr\"\n name=\"ARR\"\n stroke={CHART_COLORS[1]}\n strokeWidth={2}\n dot={false}\n activeDot={{ r: 6 }}\n />\n </LineChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Tenant Growth Chart (Area Chart)\n// ============================================================================\n\ninterface TenantGrowthChartProps {\n data: TenantGrowthPoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function TenantGrowthChart({ data, loading, className }: TenantGrowthChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Tenant Growth</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[250px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={250}>\n <AreaChart data={data} margin={{ top: 5, right: 30, left: 20, bottom: 5 }}>\n <CartesianGrid strokeDasharray=\"3 3\" className=\"stroke-border\" />\n <XAxis\n dataKey=\"date\"\n className=\"text-xs fill-muted-foreground\"\n tick={{ fill: 'currentColor' }}\n />\n <YAxis className=\"text-xs fill-muted-foreground\" tick={{ fill: 'currentColor' }} />\n <Tooltip\n contentStyle={CHART_TOOLTIP_CONTENT_STYLE}\n labelStyle={CHART_TOOLTIP_LABEL_STYLE}\n />\n <Legend />\n <Area\n type=\"monotone\"\n dataKey=\"new\"\n name=\"New\"\n stackId=\"1\"\n stroke={CHART_COLORS[0]}\n fill={CHART_COLORS[0]}\n fillOpacity={0.6}\n />\n <Area\n type=\"monotone\"\n dataKey=\"active\"\n name=\"Active\"\n stackId=\"2\"\n stroke={CHART_COLORS[1]}\n fill={CHART_COLORS[1]}\n fillOpacity={0.6}\n />\n <Area\n type=\"monotone\"\n dataKey=\"churned\"\n name=\"Churned\"\n stackId=\"3\"\n stroke={CHART_COLORS[3]}\n fill={CHART_COLORS[3]}\n fillOpacity={0.6}\n />\n </AreaChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Product Usage Chart (Pie Chart)\n// ============================================================================\n\ninterface ProductUsageChartProps {\n data: ProductUsagePoint[];\n loading?: boolean;\n className?: string;\n}\n\nexport function ProductUsageChart({ data, loading, className }: ProductUsageChartProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Product Usage</CardTitle>\n </CardHeader>\n <CardContent>\n {loading ? (\n <Skeleton className=\"h-[250px] w-full\" />\n ) : (\n <ResponsiveContainer width=\"100%\" height={250}>\n <PieChart>\n <Pie\n data={data}\n dataKey=\"tenants\"\n nameKey=\"product\"\n cx=\"50%\"\n cy=\"50%\"\n outerRadius={80}\n label={({ name, percent }: { name: string; percent: number }) =>\n `${name} (${(percent * 100).toFixed(0)}%)`\n }\n labelLine={false}\n >\n {data.map((entry, index) => (\n <Cell\n key={`cell-${index}`}\n fill={entry.color || CHART_COLORS[index % CHART_COLORS.length]}\n />\n ))}\n </Pie>\n <Tooltip\n contentStyle={CHART_TOOLTIP_CONTENT_STYLE}\n formatter={(value: number) => [formatNumber(value) + ' tenants', '']}\n />\n <Legend />\n </PieChart>\n </ResponsiveContainer>\n )}\n </CardContent>\n </Card>\n );\n}\n\n// ============================================================================\n// Quick Links Card\n// ============================================================================\n\ninterface QuickLink {\n to: string;\n icon: React.ReactNode;\n label: string;\n description?: string;\n}\n\ninterface QuickLinksProps {\n links: QuickLink[];\n className?: string;\n}\n\nexport function QuickLinks({ links, className }: QuickLinksProps) {\n return (\n <Card className={className}>\n <CardHeader>\n <CardTitle className=\"text-lg\">Quick Actions</CardTitle>\n </CardHeader>\n <CardContent>\n <div className=\"grid grid-cols-1 gap-3 sm:grid-cols-2\">\n {links.map((link) => (\n <a\n key={link.to}\n href={link.to}\n className={cn(\n 'flex items-center gap-3 rounded-lg border border-border-subtle p-3',\n 'transition-colors hover:bg-accent'\n )}\n >\n <div className=\"rounded-md bg-action-primary-bg/10 p-2 text-primary\">{link.icon}</div>\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-medium text-text-primary\">{link.label}</p>\n {link.description && (\n <p className=\"truncate text-xs text-text-secondary\">{link.description}</p>\n )}\n </div>\n </a>\n ))}\n </div>\n </CardContent>\n </Card>\n );\n}\n"],"mappings":";;;;;AAoPA,SAAgB,EAAW,EAAE,UAAO,gBAA8B;AAChE,QACE,kBAAC,GAAD;EAAiB;YAAjB,CACE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAW,WAAU;aAAU;GAAyB,CAAA,EAC7C,CAAA,EACb,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAM,KAAK,MACV,kBAAC,KAAD;IAEE,MAAM,EAAK;IACX,WAAW,EACT,sEACA,oCACD;cANH,CAQE,kBAAC,OAAD;KAAK,WAAU;eAAuD,EAAK;KAAW,CAAA,EACtF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAAiC,EAAK;MAAU,CAAA,EAC5D,EAAK,eACJ,kBAAC,KAAD;MAAG,WAAU;gBAAwC,EAAK;MAAgB,CAAA,CAExE;OACJ;MAdG,EAAK,GAcR,CACJ;GACE,CAAA,EACM,CAAA,CACT"}
@@ -31,7 +31,7 @@ function l({ open: o, onClose: l, onConfirm: u, title: d, description: f, confir
31
31
  return /* @__PURE__ */ c("div", {
32
32
  className: "fixed inset-0 z-50 flex items-center justify-center",
33
33
  children: [/* @__PURE__ */ s("div", {
34
- className: "fixed inset-0 bg-black/50 transition-opacity",
34
+ className: "fixed inset-0 bg-overlay transition-opacity",
35
35
  onClick: g ? void 0 : l,
36
36
  "aria-hidden": "true"
37
37
  }), /* @__PURE__ */ c("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../src/admin/components/ConfirmDialog.tsx"],"sourcesContent":["import { useEffect, useRef, type ReactNode } from 'react';\nimport { AlertTriangle, Loader2 } from 'lucide-react';\nimport { Button } from './ui';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ConfirmDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Callback when the dialog should close */\n onClose: () => void;\n /** Callback when confirmed */\n onConfirm: () => void | Promise<void>;\n /** Dialog title */\n title: string;\n /** Dialog description */\n description: ReactNode;\n /** Confirm button text */\n confirmLabel?: string;\n /** Cancel button text */\n cancelLabel?: string;\n /** Variant for styling */\n variant?: 'default' | 'destructive';\n /** Loading state */\n loading?: boolean;\n /** Icon to display */\n icon?: ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ConfirmDialog - Modal dialog for confirming actions\n *\n * Use for destructive actions like deleting, or important state changes.\n */\nexport function ConfirmDialog({\n open,\n onClose,\n onConfirm,\n title,\n description,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n variant = 'default',\n loading = false,\n icon,\n}: ConfirmDialogProps) {\n const confirmRef = useRef<HTMLButtonElement>(null);\n\n // Handle escape key\n useEffect(() => {\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && open && !loading) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open, onClose, loading]);\n\n // Focus confirm button when opened\n useEffect(() => {\n if (open) {\n // Small delay to ensure dialog is rendered\n const timer = setTimeout(() => {\n confirmRef.current?.focus();\n }, 50);\n return () => clearTimeout(timer);\n }\n }, [open]);\n\n // Prevent body scroll when open\n useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [open]);\n\n if (!open) return null;\n\n const handleConfirm = async () => {\n await onConfirm();\n };\n\n const isDestructive = variant === 'destructive';\n const defaultIcon = isDestructive ? <AlertTriangle className=\"h-6 w-6 text-status-error-text\" /> : null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"fixed inset-0 bg-black/50 transition-opacity\"\n onClick={loading ? undefined : onClose}\n aria-hidden=\"true\"\n />\n\n {/* Dialog */}\n <div\n className={cn(\n 'relative z-10 w-full max-w-md rounded-lg bg-bg-surface p-6 shadow-xl',\n 'animate-in zoom-in-95 fade-in duration-200'\n )}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"confirm-dialog-title\"\n aria-describedby=\"confirm-dialog-description\"\n >\n {/* Icon & Content */}\n <div className=\"flex gap-4\">\n {(icon || defaultIcon) && (\n <div\n className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center rounded-full',\n isDestructive ? 'bg-status-error-bg/10' : 'bg-action-primary-bg/10'\n )}\n >\n {icon || defaultIcon}\n </div>\n )}\n <div className=\"flex-1\">\n <h3 id=\"confirm-dialog-title\" className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h3>\n <div id=\"confirm-dialog-description\" className=\"mt-2 text-sm text-text-secondary\">\n {description}\n </div>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"mt-6 flex justify-end gap-3\">\n <Button variant=\"outline\" onClick={onClose} disabled={loading}>\n {cancelLabel}\n </Button>\n <Button\n ref={confirmRef}\n variant={isDestructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={loading}\n >\n {loading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Delete Confirmation Helper\n// ============================================================================\n\nexport interface DeleteConfirmDialogProps {\n open: boolean;\n onClose: () => void;\n onConfirm: () => void | Promise<void>;\n /** Name of item being deleted */\n itemName: string;\n /** Type of item (e.g., \"tenant\", \"user\") */\n itemType: string;\n loading?: boolean;\n}\n\n/**\n * DeleteConfirmDialog - Pre-configured dialog for delete confirmations\n */\nexport function DeleteConfirmDialog({\n open,\n onClose,\n onConfirm,\n itemName,\n itemType,\n loading,\n}: DeleteConfirmDialogProps) {\n return (\n <ConfirmDialog\n open={open}\n onClose={onClose}\n onConfirm={onConfirm}\n title={`Delete ${itemType}?`}\n description={\n <>\n Are you sure you want to delete <strong className=\"text-text-primary\">{itemName}</strong>?\n This action cannot be undone.\n </>\n }\n confirmLabel=\"Delete\"\n variant=\"destructive\"\n loading={loading}\n />\n );\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,EAAc,EAC5B,SACA,YACA,cACA,UACA,gBACA,kBAAe,WACf,iBAAc,UACd,aAAU,WACV,aAAU,IACV,WACqB;CACrB,IAAM,IAAa,EAA0B,KAAK;AAoClD,KAjCA,QAAgB;EACd,IAAM,KAAgB,MAAqB;AACzC,GAAI,EAAE,QAAQ,YAAY,KAAQ,CAAC,KACjC,GAAS;;AAIb,SADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IACjE;EAAC;EAAM;EAAS;EAAQ,CAAC,EAG5B,QAAgB;AACd,MAAI,GAAM;GAER,IAAM,IAAQ,iBAAiB;AAC7B,MAAW,SAAS,OAAO;MAC1B,GAAG;AACN,gBAAa,aAAa,EAAM;;IAEjC,CAAC,EAAK,CAAC,EAGV,SACM,IACF,SAAS,KAAK,MAAM,WAAW,WAE/B,SAAS,KAAK,MAAM,WAAW,UAEpB;AACX,WAAS,KAAK,MAAM,WAAW;KAEhC,CAAC,EAAK,CAAC,EAEN,CAAC,EAAM,QAAO;CAElB,IAAM,IAAgB,YAAY;AAChC,QAAM,GAAW;IAGb,IAAgB,MAAY,eAC5B,IAAc,IAAgB,kBAAC,GAAD,EAAe,WAAU,kCAAmC,CAAA,GAAG;AAEnG,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GACE,WAAU;GACV,SAAS,IAAU,KAAA,IAAY;GAC/B,eAAY;GACZ,CAAA,EAGF,kBAAC,OAAD;GACE,WAAW,EACT,wEACA,6CACD;GACD,MAAK;GACL,cAAW;GACX,mBAAgB;GAChB,oBAAiB;aARnB,CAWE,kBAAC,OAAD;IAAK,WAAU;cAAf,EACI,KAAQ,MACR,kBAAC,OAAD;KACE,WAAW,EACT,oEACA,IAAgB,0BAA0B,0BAC3C;eAEA,KAAQ;KACL,CAAA,EAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,IAAG;MAAuB,WAAU;gBACrC;MACE,CAAA,EACL,kBAAC,OAAD;MAAK,IAAG;MAA6B,WAAU;gBAC5C;MACG,CAAA,CACF;OACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,SAAS;KAAS,UAAU;eACnD;KACM,CAAA,EACT,kBAAC,GAAD;KACE,KAAK;KACL,SAAS,IAAgB,gBAAgB;KACzC,SAAS;KACT,UAAU;eAJZ,CAMG,KAAW,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,EAC5D,EACM;OACL;MACF;KACF;;;AAsBV,SAAgB,EAAoB,EAClC,SACA,YACA,cACA,aACA,aACA,cAC2B;AAC3B,QACE,kBAAC,GAAD;EACQ;EACG;EACE;EACX,OAAO,UAAU,EAAS;EAC1B,aACE,kBAAA,GAAA,EAAA,UAAA;GAAE;GACgC,kBAAC,UAAD;IAAQ,WAAU;cAAqB;IAAkB,CAAA;;GAExF,EAAA,CAAA;EAEL,cAAa;EACb,SAAQ;EACC;EACT,CAAA"}
1
+ {"version":3,"file":"ConfirmDialog.js","names":[],"sources":["../../../src/admin/components/ConfirmDialog.tsx"],"sourcesContent":["import { useEffect, useRef, type ReactNode } from 'react';\nimport { AlertTriangle, Loader2 } from 'lucide-react';\nimport { Button } from './ui';\nimport { cn } from '../utils/cn';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ConfirmDialogProps {\n /** Whether the dialog is open */\n open: boolean;\n /** Callback when the dialog should close */\n onClose: () => void;\n /** Callback when confirmed */\n onConfirm: () => void | Promise<void>;\n /** Dialog title */\n title: string;\n /** Dialog description */\n description: ReactNode;\n /** Confirm button text */\n confirmLabel?: string;\n /** Cancel button text */\n cancelLabel?: string;\n /** Variant for styling */\n variant?: 'default' | 'destructive';\n /** Loading state */\n loading?: boolean;\n /** Icon to display */\n icon?: ReactNode;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ConfirmDialog - Modal dialog for confirming actions\n *\n * Use for destructive actions like deleting, or important state changes.\n */\nexport function ConfirmDialog({\n open,\n onClose,\n onConfirm,\n title,\n description,\n confirmLabel = 'Confirm',\n cancelLabel = 'Cancel',\n variant = 'default',\n loading = false,\n icon,\n}: ConfirmDialogProps) {\n const confirmRef = useRef<HTMLButtonElement>(null);\n\n // Handle escape key\n useEffect(() => {\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && open && !loading) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open, onClose, loading]);\n\n // Focus confirm button when opened\n useEffect(() => {\n if (open) {\n // Small delay to ensure dialog is rendered\n const timer = setTimeout(() => {\n confirmRef.current?.focus();\n }, 50);\n return () => clearTimeout(timer);\n }\n }, [open]);\n\n // Prevent body scroll when open\n useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [open]);\n\n if (!open) return null;\n\n const handleConfirm = async () => {\n await onConfirm();\n };\n\n const isDestructive = variant === 'destructive';\n const defaultIcon = isDestructive ? <AlertTriangle className=\"h-6 w-6 text-status-error-text\" /> : null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div\n className=\"fixed inset-0 bg-overlay transition-opacity\"\n onClick={loading ? undefined : onClose}\n aria-hidden=\"true\"\n />\n\n {/* Dialog */}\n <div\n className={cn(\n 'relative z-10 w-full max-w-md rounded-lg bg-bg-surface p-6 shadow-xl',\n 'animate-in zoom-in-95 fade-in duration-200'\n )}\n role=\"alertdialog\"\n aria-modal=\"true\"\n aria-labelledby=\"confirm-dialog-title\"\n aria-describedby=\"confirm-dialog-description\"\n >\n {/* Icon & Content */}\n <div className=\"flex gap-4\">\n {(icon || defaultIcon) && (\n <div\n className={cn(\n 'flex h-12 w-12 shrink-0 items-center justify-center rounded-full',\n isDestructive ? 'bg-status-error-bg/10' : 'bg-action-primary-bg/10'\n )}\n >\n {icon || defaultIcon}\n </div>\n )}\n <div className=\"flex-1\">\n <h3 id=\"confirm-dialog-title\" className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h3>\n <div id=\"confirm-dialog-description\" className=\"mt-2 text-sm text-text-secondary\">\n {description}\n </div>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"mt-6 flex justify-end gap-3\">\n <Button variant=\"outline\" onClick={onClose} disabled={loading}>\n {cancelLabel}\n </Button>\n <Button\n ref={confirmRef}\n variant={isDestructive ? 'destructive' : 'default'}\n onClick={handleConfirm}\n disabled={loading}\n >\n {loading && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {confirmLabel}\n </Button>\n </div>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Delete Confirmation Helper\n// ============================================================================\n\nexport interface DeleteConfirmDialogProps {\n open: boolean;\n onClose: () => void;\n onConfirm: () => void | Promise<void>;\n /** Name of item being deleted */\n itemName: string;\n /** Type of item (e.g., \"tenant\", \"user\") */\n itemType: string;\n loading?: boolean;\n}\n\n/**\n * DeleteConfirmDialog - Pre-configured dialog for delete confirmations\n */\nexport function DeleteConfirmDialog({\n open,\n onClose,\n onConfirm,\n itemName,\n itemType,\n loading,\n}: DeleteConfirmDialogProps) {\n return (\n <ConfirmDialog\n open={open}\n onClose={onClose}\n onConfirm={onConfirm}\n title={`Delete ${itemType}?`}\n description={\n <>\n Are you sure you want to delete <strong className=\"text-text-primary\">{itemName}</strong>?\n This action cannot be undone.\n </>\n }\n confirmLabel=\"Delete\"\n variant=\"destructive\"\n loading={loading}\n />\n );\n}\n"],"mappings":";;;;;;AAyCA,SAAgB,EAAc,EAC5B,SACA,YACA,cACA,UACA,gBACA,kBAAe,WACf,iBAAc,UACd,aAAU,WACV,aAAU,IACV,WACqB;CACrB,IAAM,IAAa,EAA0B,KAAK;AAoClD,KAjCA,QAAgB;EACd,IAAM,KAAgB,MAAqB;AACzC,GAAI,EAAE,QAAQ,YAAY,KAAQ,CAAC,KACjC,GAAS;;AAIb,SADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IACjE;EAAC;EAAM;EAAS;EAAQ,CAAC,EAG5B,QAAgB;AACd,MAAI,GAAM;GAER,IAAM,IAAQ,iBAAiB;AAC7B,MAAW,SAAS,OAAO;MAC1B,GAAG;AACN,gBAAa,aAAa,EAAM;;IAEjC,CAAC,EAAK,CAAC,EAGV,SACM,IACF,SAAS,KAAK,MAAM,WAAW,WAE/B,SAAS,KAAK,MAAM,WAAW,UAEpB;AACX,WAAS,KAAK,MAAM,WAAW;KAEhC,CAAC,EAAK,CAAC,EAEN,CAAC,EAAM,QAAO;CAElB,IAAM,IAAgB,YAAY;AAChC,QAAM,GAAW;IAGb,IAAgB,MAAY,eAC5B,IAAc,IAAgB,kBAAC,GAAD,EAAe,WAAU,kCAAmC,CAAA,GAAG;AAEnG,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GACE,WAAU;GACV,SAAS,IAAU,KAAA,IAAY;GAC/B,eAAY;GACZ,CAAA,EAGF,kBAAC,OAAD;GACE,WAAW,EACT,wEACA,6CACD;GACD,MAAK;GACL,cAAW;GACX,mBAAgB;GAChB,oBAAiB;aARnB,CAWE,kBAAC,OAAD;IAAK,WAAU;cAAf,EACI,KAAQ,MACR,kBAAC,OAAD;KACE,WAAW,EACT,oEACA,IAAgB,0BAA0B,0BAC3C;eAEA,KAAQ;KACL,CAAA,EAER,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,IAAG;MAAuB,WAAU;gBACrC;MACE,CAAA,EACL,kBAAC,OAAD;MAAK,IAAG;MAA6B,WAAU;gBAC5C;MACG,CAAA,CACF;OACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAQ,SAAQ;KAAU,SAAS;KAAS,UAAU;eACnD;KACM,CAAA,EACT,kBAAC,GAAD;KACE,KAAK;KACL,SAAS,IAAgB,gBAAgB;KACzC,SAAS;KACT,UAAU;eAJZ,CAMG,KAAW,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,EAC5D,EACM;OACL;MACF;KACF;;;AAsBV,SAAgB,EAAoB,EAClC,SACA,YACA,cACA,aACA,aACA,cAC2B;AAC3B,QACE,kBAAC,GAAD;EACQ;EACG;EACE;EACX,OAAO,UAAU,EAAS;EAC1B,aACE,kBAAA,GAAA,EAAA,UAAA;GAAE;GACgC,kBAAC,UAAD;IAAQ,WAAU;cAAqB;IAAkB,CAAA;;GAExF,EAAA,CAAA;EAEL,cAAa;EACb,SAAQ;EACC;EACT,CAAA"}
@@ -14,13 +14,13 @@ function l({ columns: t, data: r, keyExtractor: i, loading: a, skeletonRows: o =
14
14
  right: "text-right"
15
15
  };
16
16
  return /* @__PURE__ */ s("div", {
17
- className: e("overflow-hidden rounded-lg border border-border-subtle", m),
17
+ className: e("overflow-hidden rounded-card border border-border-seam", m),
18
18
  children: /* @__PURE__ */ s("div", {
19
19
  className: "overflow-x-auto",
20
20
  children: /* @__PURE__ */ c("table", {
21
21
  className: "w-full border-collapse",
22
22
  children: [/* @__PURE__ */ s("thead", { children: /* @__PURE__ */ s("tr", {
23
- className: "border-b border-border-subtle bg-bg-sunken/50",
23
+ className: "border-b border-border-seam bg-bg-sunken/40",
24
24
  children: t.map((t) => /* @__PURE__ */ s("th", {
25
25
  className: e("px-4 py-3 text-sm font-medium text-text-secondary", g[t.align || "left"], t.sortable && "cursor-pointer select-none hover:text-text-primary", t.width),
26
26
  style: t.width ? { width: t.width } : void 0,
@@ -34,10 +34,10 @@ function l({ columns: t, data: r, keyExtractor: i, loading: a, skeletonRows: o =
34
34
  })
35
35
  }, t.key))
36
36
  }) }), /* @__PURE__ */ s("tbody", { children: a ? Array.from({ length: o }).map((e, r) => /* @__PURE__ */ s("tr", {
37
- className: "border-b border-border-subtle last:border-0",
37
+ className: "border-b border-border-seam last:border-0",
38
38
  children: t.map((e) => /* @__PURE__ */ s("td", {
39
39
  className: "px-4 py-3",
40
- children: /* @__PURE__ */ s(n, { className: "h-5 w-full" })
40
+ children: /* @__PURE__ */ s(n, { className: "h-5 w-full motion-safe:animate-vc-shimmer" })
41
41
  }, e.key))
42
42
  }, r)) : r.length === 0 ? /* @__PURE__ */ s("tr", { children: /* @__PURE__ */ s("td", {
43
43
  colSpan: t.length,
@@ -47,7 +47,7 @@ function l({ columns: t, data: r, keyExtractor: i, loading: a, skeletonRows: o =
47
47
  children: "No data available"
48
48
  })
49
49
  }) }) : r.map((n, r) => /* @__PURE__ */ s("tr", {
50
- className: e("border-b border-border-subtle last:border-0 transition-colors", u && "cursor-pointer hover:bg-bg-sunken/50"),
50
+ className: e("border-b border-border-seam last:border-0 transition-colors", u && "cursor-pointer hover:bg-bg-sunken/40"),
51
51
  onClick: () => u?.(n),
52
52
  children: t.map((t) => /* @__PURE__ */ s("td", {
53
53
  className: e("px-4 py-3 text-sm", g[t.align || "left"]),
@@ -1 +1 @@
1
- {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/admin/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\nimport { Button, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\n\ninterface Column<T> {\n /** Column key (used for accessing data) */\n key: string;\n /** Column header */\n header: string;\n /** Custom render function */\n render?: (item: T, index: number) => ReactNode;\n /** Column width */\n width?: string;\n /** Align content */\n align?: 'left' | 'center' | 'right';\n /** Sortable */\n sortable?: boolean;\n}\n\ninterface DataTableProps<T> {\n /** Column definitions */\n columns: Column<T>[];\n /** Data to display */\n data: T[];\n /** Key extractor for rows */\n keyExtractor: (item: T) => string;\n /** Loading state */\n loading?: boolean;\n /** Number of skeleton rows when loading */\n skeletonRows?: number;\n /** Empty state content */\n emptyState?: ReactNode;\n /** On row click handler */\n onRowClick?: (item: T) => void;\n /** Current sort column */\n sortBy?: string;\n /** Sort direction */\n sortOrder?: 'asc' | 'desc';\n /** On sort change */\n onSort?: (column: string, order: 'asc' | 'desc') => void;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Reusable data table component\n */\nexport function DataTable<T>({\n columns,\n data,\n keyExtractor,\n loading,\n skeletonRows = 5,\n emptyState,\n onRowClick,\n sortBy,\n sortOrder,\n onSort,\n className,\n}: DataTableProps<T>) {\n const handleSort = (column: Column<T>) => {\n if (!column.sortable || !onSort) return;\n const newOrder = sortBy === column.key && sortOrder === 'asc' ? 'desc' : 'asc';\n onSort(column.key, newOrder);\n };\n\n const alignClasses = {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right',\n };\n\n return (\n <div className={cn('overflow-hidden rounded-lg border border-border-subtle', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse\">\n <thead>\n <tr className=\"border-b border-border-subtle bg-bg-sunken/50\">\n {columns.map((column) => (\n <th\n key={column.key}\n className={cn(\n 'px-4 py-3 text-sm font-medium text-text-secondary',\n alignClasses[column.align || 'left'],\n column.sortable && 'cursor-pointer select-none hover:text-text-primary',\n column.width\n )}\n style={column.width ? { width: column.width } : undefined}\n onClick={() => handleSort(column)}\n >\n <div className=\"flex items-center gap-2\">\n {column.header}\n {column.sortable && sortBy === column.key && (\n <span className=\"text-xs\">{sortOrder === 'asc' ? '↑' : '↓'}</span>\n )}\n </div>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {loading ? (\n Array.from({ length: skeletonRows }).map((_, rowIndex) => (\n <tr key={rowIndex} className=\"border-b border-border-subtle last:border-0\">\n {columns.map((column) => (\n <td key={column.key} className=\"px-4 py-3\">\n <Skeleton className=\"h-5 w-full\" />\n </td>\n ))}\n </tr>\n ))\n ) : data.length === 0 ? (\n <tr>\n <td colSpan={columns.length} className=\"px-4 py-8\">\n {emptyState || (\n <div className=\"text-center text-sm text-text-secondary\">\n No data available\n </div>\n )}\n </td>\n </tr>\n ) : (\n data.map((item, index) => (\n <tr\n key={keyExtractor(item)}\n className={cn(\n 'border-b border-border-subtle last:border-0 transition-colors',\n onRowClick && 'cursor-pointer hover:bg-bg-sunken/50'\n )}\n onClick={() => onRowClick?.(item)}\n >\n {columns.map((column) => (\n <td\n key={column.key}\n className={cn('px-4 py-3 text-sm', alignClasses[column.align || 'left'])}\n >\n {column.render\n ? column.render(item, index)\n : String((item as Record<string, unknown>)[column.key] ?? '')}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n\ninterface PaginationProps {\n /** Current page (1-indexed) */\n page: number;\n /** Total pages */\n totalPages: number;\n /** Total items */\n totalItems: number;\n /** Items per page */\n pageSize: number;\n /** On page change */\n onPageChange: (page: number) => void;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Pagination controls\n */\nexport function Pagination({\n page,\n totalPages,\n totalItems,\n pageSize,\n onPageChange,\n className,\n}: PaginationProps) {\n const startItem = (page - 1) * pageSize + 1;\n const endItem = Math.min(page * pageSize, totalItems);\n\n return (\n <div className={cn('flex items-center justify-between px-4 py-3', className)}>\n <div className=\"text-sm text-text-secondary\">\n Showing {startItem} to {endItem} of {totalItems} results\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(1)}\n disabled={page === 1}\n aria-label=\"First page\"\n >\n <ChevronsLeft className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(page - 1)}\n disabled={page === 1}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <span className=\"px-3 text-sm\">\n Page {page} of {totalPages}\n </span>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(page + 1)}\n disabled={page === totalPages}\n aria-label=\"Next page\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(totalPages)}\n disabled={page === totalPages}\n aria-label=\"Last page\"\n >\n <ChevronsRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"DataTable.js","names":[],"sources":["../../../src/admin/components/DataTable.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react';\nimport { Button, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\n\ninterface Column<T> {\n /** Column key (used for accessing data) */\n key: string;\n /** Column header */\n header: string;\n /** Custom render function */\n render?: (item: T, index: number) => ReactNode;\n /** Column width */\n width?: string;\n /** Align content */\n align?: 'left' | 'center' | 'right';\n /** Sortable */\n sortable?: boolean;\n}\n\ninterface DataTableProps<T> {\n /** Column definitions */\n columns: Column<T>[];\n /** Data to display */\n data: T[];\n /** Key extractor for rows */\n keyExtractor: (item: T) => string;\n /** Loading state */\n loading?: boolean;\n /** Number of skeleton rows when loading */\n skeletonRows?: number;\n /** Empty state content */\n emptyState?: ReactNode;\n /** On row click handler */\n onRowClick?: (item: T) => void;\n /** Current sort column */\n sortBy?: string;\n /** Sort direction */\n sortOrder?: 'asc' | 'desc';\n /** On sort change */\n onSort?: (column: string, order: 'asc' | 'desc') => void;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Reusable data table component\n */\nexport function DataTable<T>({\n columns,\n data,\n keyExtractor,\n loading,\n skeletonRows = 5,\n emptyState,\n onRowClick,\n sortBy,\n sortOrder,\n onSort,\n className,\n}: DataTableProps<T>) {\n const handleSort = (column: Column<T>) => {\n if (!column.sortable || !onSort) return;\n const newOrder = sortBy === column.key && sortOrder === 'asc' ? 'desc' : 'asc';\n onSort(column.key, newOrder);\n };\n\n const alignClasses = {\n left: 'text-left',\n center: 'text-center',\n right: 'text-right',\n };\n\n return (\n <div className={cn('overflow-hidden rounded-card border border-border-seam', className)}>\n <div className=\"overflow-x-auto\">\n <table className=\"w-full border-collapse\">\n <thead>\n <tr className=\"border-b border-border-seam bg-bg-sunken/40\">\n {columns.map((column) => (\n <th\n key={column.key}\n className={cn(\n 'px-4 py-3 text-sm font-medium text-text-secondary',\n alignClasses[column.align || 'left'],\n column.sortable && 'cursor-pointer select-none hover:text-text-primary',\n column.width\n )}\n style={column.width ? { width: column.width } : undefined}\n onClick={() => handleSort(column)}\n >\n <div className=\"flex items-center gap-2\">\n {column.header}\n {column.sortable && sortBy === column.key && (\n <span className=\"text-xs\">{sortOrder === 'asc' ? '↑' : '↓'}</span>\n )}\n </div>\n </th>\n ))}\n </tr>\n </thead>\n <tbody>\n {loading ? (\n Array.from({ length: skeletonRows }).map((_, rowIndex) => (\n <tr key={rowIndex} className=\"border-b border-border-seam last:border-0\">\n {columns.map((column) => (\n <td key={column.key} className=\"px-4 py-3\">\n <Skeleton className=\"h-5 w-full motion-safe:animate-vc-shimmer\" />\n </td>\n ))}\n </tr>\n ))\n ) : data.length === 0 ? (\n <tr>\n <td colSpan={columns.length} className=\"px-4 py-8\">\n {emptyState || (\n <div className=\"text-center text-sm text-text-secondary\">\n No data available\n </div>\n )}\n </td>\n </tr>\n ) : (\n data.map((item, index) => (\n <tr\n key={keyExtractor(item)}\n className={cn(\n 'border-b border-border-seam last:border-0 transition-colors',\n onRowClick && 'cursor-pointer hover:bg-bg-sunken/40'\n )}\n onClick={() => onRowClick?.(item)}\n >\n {columns.map((column) => (\n <td\n key={column.key}\n className={cn('px-4 py-3 text-sm', alignClasses[column.align || 'left'])}\n >\n {column.render\n ? column.render(item, index)\n : String((item as Record<string, unknown>)[column.key] ?? '')}\n </td>\n ))}\n </tr>\n ))\n )}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n\ninterface PaginationProps {\n /** Current page (1-indexed) */\n page: number;\n /** Total pages */\n totalPages: number;\n /** Total items */\n totalItems: number;\n /** Items per page */\n pageSize: number;\n /** On page change */\n onPageChange: (page: number) => void;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Pagination controls\n */\nexport function Pagination({\n page,\n totalPages,\n totalItems,\n pageSize,\n onPageChange,\n className,\n}: PaginationProps) {\n const startItem = (page - 1) * pageSize + 1;\n const endItem = Math.min(page * pageSize, totalItems);\n\n return (\n <div className={cn('flex items-center justify-between px-4 py-3', className)}>\n <div className=\"text-sm text-text-secondary\">\n Showing {startItem} to {endItem} of {totalItems} results\n </div>\n <div className=\"flex items-center gap-1\">\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(1)}\n disabled={page === 1}\n aria-label=\"First page\"\n >\n <ChevronsLeft className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(page - 1)}\n disabled={page === 1}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"h-4 w-4\" />\n </Button>\n <span className=\"px-3 text-sm\">\n Page {page} of {totalPages}\n </span>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(page + 1)}\n disabled={page === totalPages}\n aria-label=\"Next page\"\n >\n <ChevronRight className=\"h-4 w-4\" />\n </Button>\n <Button\n variant=\"outline\"\n size=\"icon\"\n onClick={() => onPageChange(totalPages)}\n disabled={page === totalPages}\n aria-label=\"Last page\"\n >\n <ChevronsRight className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -21,7 +21,7 @@ function c({ open: r, onClose: c, title: l, description: u, children: d, width:
21
21
  }), [r]), r ? /* @__PURE__ */ o("div", {
22
22
  className: "fixed inset-0 z-50",
23
23
  children: [/* @__PURE__ */ a("div", {
24
- className: "fixed inset-0 bg-black/50 transition-opacity",
24
+ className: "fixed inset-0 bg-overlay transition-opacity",
25
25
  onClick: c,
26
26
  "aria-hidden": "true"
27
27
  }), /* @__PURE__ */ o("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"FormSheet.js","names":[],"sources":["../../../src/admin/components/FormSheet.tsx"],"sourcesContent":["import * as React from 'react';\nimport { X, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\nimport { Button } from './ui';\n\n/**\n * FormSheet props\n */\nexport interface FormSheetProps {\n /** Whether the sheet is open */\n open: boolean;\n /** Callback when the sheet should close */\n onClose: () => void;\n /** Title of the sheet */\n title: string;\n /** Optional description */\n description?: string;\n /** Form content */\n children: React.ReactNode;\n /** Width of the sheet */\n width?: 'sm' | 'md' | 'lg' | 'xl';\n /** Optional footer actions */\n footer?: React.ReactNode;\n}\n\nconst widthClasses = {\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n xl: 'max-w-xl',\n};\n\n/**\n * FormSheet - Slide-over panel for forms\n *\n * A side panel component for displaying forms that slides in from the right.\n * Includes backdrop overlay, header with close button, and optional footer.\n */\nexport function FormSheet({\n open,\n onClose,\n title,\n description,\n children,\n width = 'md',\n footer,\n}: FormSheetProps) {\n // Handle escape key\n React.useEffect(() => {\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && open) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open, onClose]);\n\n // Prevent body scroll when open\n React.useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className=\"fixed inset-0 bg-black/50 transition-opacity\"\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Sheet */}\n <div\n className={cn(\n 'fixed inset-y-0 right-0 flex w-full flex-col bg-bg-surface shadow-xl',\n 'animate-in slide-in-from-right duration-300',\n widthClasses[width]\n )}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sheet-title\"\n >\n {/* Header */}\n <div className=\"flex items-start justify-between border-b border-border-subtle px-6 py-4\">\n <div>\n <h2 id=\"sheet-title\" className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h2>\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n <Button variant=\"ghost\" size=\"icon\" onClick={onClose} aria-label=\"Close\">\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">{children}</div>\n\n {/* Footer */}\n {footer && <div className=\"border-t border-border-subtle px-6 py-4\">{footer}</div>}\n </div>\n </div>\n );\n}\n\n/**\n * FormSheetFooter props\n */\nexport interface FormSheetFooterProps {\n /** Primary action button text */\n submitLabel?: string;\n /** Cancel button text */\n cancelLabel?: string;\n /** Callback for cancel */\n onCancel: () => void;\n /** Whether form is submitting */\n isSubmitting?: boolean;\n /** Whether submit is disabled */\n disabled?: boolean;\n /** Form ID to connect submit button */\n formId?: string;\n /** Callback for submit (if not using form) */\n onSubmit?: () => void;\n /** Variant for submit button */\n submitVariant?: 'default' | 'destructive';\n}\n\n/**\n * FormSheetFooter - Standard footer for FormSheet with cancel/submit buttons\n */\nexport function FormSheetFooter({\n submitLabel = 'Save',\n cancelLabel = 'Cancel',\n onCancel,\n isSubmitting = false,\n disabled = false,\n formId,\n onSubmit,\n submitVariant = 'default',\n}: FormSheetFooterProps) {\n return (\n <div className=\"flex items-center justify-end gap-3\">\n <Button variant=\"outline\" onClick={onCancel} disabled={isSubmitting}>\n {cancelLabel}\n </Button>\n <Button\n type={formId ? 'submit' : 'button'}\n form={formId}\n onClick={onSubmit}\n disabled={disabled || isSubmitting}\n variant={submitVariant}\n >\n {isSubmitting && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {submitLabel}\n </Button>\n </div>\n );\n}\n\n/**\n * FormSection - Group related form fields\n */\nexport interface FormSectionProps {\n /** Section title */\n title?: string;\n /** Section description */\n description?: string;\n /** Form fields */\n children: React.ReactNode;\n /** Additional className */\n className?: string;\n}\n\nexport function FormSection({ title, description, children, className }: FormSectionProps) {\n return (\n <div className={cn('space-y-4', className)}>\n {(title || description) && (\n <div className=\"space-y-1\">\n {title && <h3 className=\"text-sm font-medium text-text-primary\">{title}</h3>}\n {description && <p className=\"text-xs text-text-secondary\">{description}</p>}\n </div>\n )}\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\n/**\n * FormRow - Horizontal row for form fields\n */\nexport interface FormRowProps {\n /** Form fields */\n children: React.ReactNode;\n /** Number of columns */\n cols?: 2 | 3 | 4;\n /** Additional className */\n className?: string;\n}\n\nexport function FormRow({ children, cols = 2, className }: FormRowProps) {\n const colsClass = {\n 2: 'grid-cols-2',\n 3: 'grid-cols-3',\n 4: 'grid-cols-4',\n };\n return <div className={cn('grid gap-4', colsClass[cols], className)}>{children}</div>;\n}\n"],"mappings":";;;;;;AAyBA,IAAM,IAAe;CACnB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL;AAQD,SAAgB,EAAU,EACxB,SACA,YACA,UACA,gBACA,aACA,WAAQ,MACR,aACiB;AA0BjB,QAxBA,EAAM,gBAAgB;EACpB,IAAM,KAAgB,MAAqB;AACzC,GAAI,EAAE,QAAQ,YAAY,KACxB,GAAS;;AAIb,SADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IACjE,CAAC,GAAM,EAAQ,CAAC,EAGnB,EAAM,iBACA,IACF,SAAS,KAAK,MAAM,WAAW,WAE/B,SAAS,KAAK,MAAM,WAAW,UAEpB;AACX,WAAS,KAAK,MAAM,WAAW;KAEhC,CAAC,EAAK,CAAC,EAEL,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GACE,WAAU;GACV,SAAS;GACT,eAAY;GACZ,CAAA,EAGF,kBAAC,OAAD;GACE,WAAW,EACT,wEACA,+CACA,EAAa,GACd;GACD,MAAK;GACL,cAAW;GACX,mBAAgB;aARlB;IAWE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,IAAG;MAAc,WAAU;gBAC5B;MACE,CAAA,EACJ,KAAe,kBAAC,KAAD;MAAG,WAAU;gBAAoC;MAAgB,CAAA,CAC7E,EAAA,CAAA,EACN,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAO,SAAS;MAAS,cAAW;gBAC/D,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;KAAoC;KAAe,CAAA;IAGjE,KAAU,kBAAC,OAAD;KAAK,WAAU;eAA2C;KAAa,CAAA;IAC9E;KACF;MAzCU;;AAsEpB,SAAgB,EAAgB,EAC9B,iBAAc,QACd,iBAAc,UACd,aACA,kBAAe,IACf,cAAW,IACX,WACA,aACA,mBAAgB,aACO;AACvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;GAAU,UAAU;aACpD;GACM,CAAA,EACT,kBAAC,GAAD;GACE,MAAM,IAAS,WAAW;GAC1B,MAAM;GACN,SAAS;GACT,UAAU,KAAY;GACtB,SAAS;aALX,CAOG,KAAgB,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,EACjE,EACM;KACL;;;AAkBV,SAAgB,EAAY,EAAE,UAAO,gBAAa,aAAU,gBAA+B;AACzF,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C,EACI,KAAS,MACT,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KAAS,kBAAC,MAAD;IAAI,WAAU;cAAyC;IAAW,CAAA,EAC3E,KAAe,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAgB,CAAA,CACxE;MAER,kBAAC,OAAD;GAAK,WAAU;GAAa;GAAe,CAAA,CACvC;;;AAgBV,SAAgB,EAAQ,EAAE,aAAU,UAAO,GAAG,gBAA2B;AAMvE,QAAO,kBAAC,OAAD;EAAK,WAAW,EAAG,cALR;GAChB,GAAG;GACH,GAAG;GACH,GAAG;GACJ,CACiD,IAAO,EAAU;EAAG;EAAe,CAAA"}
1
+ {"version":3,"file":"FormSheet.js","names":[],"sources":["../../../src/admin/components/FormSheet.tsx"],"sourcesContent":["import * as React from 'react';\nimport { X, Loader2 } from 'lucide-react';\nimport { cn } from '../utils/cn';\nimport { Button } from './ui';\n\n/**\n * FormSheet props\n */\nexport interface FormSheetProps {\n /** Whether the sheet is open */\n open: boolean;\n /** Callback when the sheet should close */\n onClose: () => void;\n /** Title of the sheet */\n title: string;\n /** Optional description */\n description?: string;\n /** Form content */\n children: React.ReactNode;\n /** Width of the sheet */\n width?: 'sm' | 'md' | 'lg' | 'xl';\n /** Optional footer actions */\n footer?: React.ReactNode;\n}\n\nconst widthClasses = {\n sm: 'max-w-sm',\n md: 'max-w-md',\n lg: 'max-w-lg',\n xl: 'max-w-xl',\n};\n\n/**\n * FormSheet - Slide-over panel for forms\n *\n * A side panel component for displaying forms that slides in from the right.\n * Includes backdrop overlay, header with close button, and optional footer.\n */\nexport function FormSheet({\n open,\n onClose,\n title,\n description,\n children,\n width = 'md',\n footer,\n}: FormSheetProps) {\n // Handle escape key\n React.useEffect(() => {\n const handleEscape = (e: KeyboardEvent) => {\n if (e.key === 'Escape' && open) {\n onClose();\n }\n };\n document.addEventListener('keydown', handleEscape);\n return () => document.removeEventListener('keydown', handleEscape);\n }, [open, onClose]);\n\n // Prevent body scroll when open\n React.useEffect(() => {\n if (open) {\n document.body.style.overflow = 'hidden';\n } else {\n document.body.style.overflow = '';\n }\n return () => {\n document.body.style.overflow = '';\n };\n }, [open]);\n\n if (!open) return null;\n\n return (\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className=\"fixed inset-0 bg-overlay transition-opacity\"\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Sheet */}\n <div\n className={cn(\n 'fixed inset-y-0 right-0 flex w-full flex-col bg-bg-surface shadow-xl',\n 'animate-in slide-in-from-right duration-300',\n widthClasses[width]\n )}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sheet-title\"\n >\n {/* Header */}\n <div className=\"flex items-start justify-between border-b border-border-subtle px-6 py-4\">\n <div>\n <h2 id=\"sheet-title\" className=\"text-lg font-semibold text-text-primary\">\n {title}\n </h2>\n {description && <p className=\"mt-1 text-sm text-text-secondary\">{description}</p>}\n </div>\n <Button variant=\"ghost\" size=\"icon\" onClick={onClose} aria-label=\"Close\">\n <X className=\"h-4 w-4\" />\n </Button>\n </div>\n\n {/* Content */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">{children}</div>\n\n {/* Footer */}\n {footer && <div className=\"border-t border-border-subtle px-6 py-4\">{footer}</div>}\n </div>\n </div>\n );\n}\n\n/**\n * FormSheetFooter props\n */\nexport interface FormSheetFooterProps {\n /** Primary action button text */\n submitLabel?: string;\n /** Cancel button text */\n cancelLabel?: string;\n /** Callback for cancel */\n onCancel: () => void;\n /** Whether form is submitting */\n isSubmitting?: boolean;\n /** Whether submit is disabled */\n disabled?: boolean;\n /** Form ID to connect submit button */\n formId?: string;\n /** Callback for submit (if not using form) */\n onSubmit?: () => void;\n /** Variant for submit button */\n submitVariant?: 'default' | 'destructive';\n}\n\n/**\n * FormSheetFooter - Standard footer for FormSheet with cancel/submit buttons\n */\nexport function FormSheetFooter({\n submitLabel = 'Save',\n cancelLabel = 'Cancel',\n onCancel,\n isSubmitting = false,\n disabled = false,\n formId,\n onSubmit,\n submitVariant = 'default',\n}: FormSheetFooterProps) {\n return (\n <div className=\"flex items-center justify-end gap-3\">\n <Button variant=\"outline\" onClick={onCancel} disabled={isSubmitting}>\n {cancelLabel}\n </Button>\n <Button\n type={formId ? 'submit' : 'button'}\n form={formId}\n onClick={onSubmit}\n disabled={disabled || isSubmitting}\n variant={submitVariant}\n >\n {isSubmitting && <Loader2 className=\"mr-2 h-4 w-4 animate-spin\" />}\n {submitLabel}\n </Button>\n </div>\n );\n}\n\n/**\n * FormSection - Group related form fields\n */\nexport interface FormSectionProps {\n /** Section title */\n title?: string;\n /** Section description */\n description?: string;\n /** Form fields */\n children: React.ReactNode;\n /** Additional className */\n className?: string;\n}\n\nexport function FormSection({ title, description, children, className }: FormSectionProps) {\n return (\n <div className={cn('space-y-4', className)}>\n {(title || description) && (\n <div className=\"space-y-1\">\n {title && <h3 className=\"text-sm font-medium text-text-primary\">{title}</h3>}\n {description && <p className=\"text-xs text-text-secondary\">{description}</p>}\n </div>\n )}\n <div className=\"space-y-4\">{children}</div>\n </div>\n );\n}\n\n/**\n * FormRow - Horizontal row for form fields\n */\nexport interface FormRowProps {\n /** Form fields */\n children: React.ReactNode;\n /** Number of columns */\n cols?: 2 | 3 | 4;\n /** Additional className */\n className?: string;\n}\n\nexport function FormRow({ children, cols = 2, className }: FormRowProps) {\n const colsClass = {\n 2: 'grid-cols-2',\n 3: 'grid-cols-3',\n 4: 'grid-cols-4',\n };\n return <div className={cn('grid gap-4', colsClass[cols], className)}>{children}</div>;\n}\n"],"mappings":";;;;;;AAyBA,IAAM,IAAe;CACnB,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;CACL;AAQD,SAAgB,EAAU,EACxB,SACA,YACA,UACA,gBACA,aACA,WAAQ,MACR,aACiB;AA0BjB,QAxBA,EAAM,gBAAgB;EACpB,IAAM,KAAgB,MAAqB;AACzC,GAAI,EAAE,QAAQ,YAAY,KACxB,GAAS;;AAIb,SADA,SAAS,iBAAiB,WAAW,EAAa,QACrC,SAAS,oBAAoB,WAAW,EAAa;IACjE,CAAC,GAAM,EAAQ,CAAC,EAGnB,EAAM,iBACA,IACF,SAAS,KAAK,MAAM,WAAW,WAE/B,SAAS,KAAK,MAAM,WAAW,UAEpB;AACX,WAAS,KAAK,MAAM,WAAW;KAEhC,CAAC,EAAK,CAAC,EAEL,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GACE,WAAU;GACV,SAAS;GACT,eAAY;GACZ,CAAA,EAGF,kBAAC,OAAD;GACE,WAAW,EACT,wEACA,+CACA,EAAa,GACd;GACD,MAAK;GACL,cAAW;GACX,mBAAgB;aARlB;IAWE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,IAAG;MAAc,WAAU;gBAC5B;MACE,CAAA,EACJ,KAAe,kBAAC,KAAD;MAAG,WAAU;gBAAoC;MAAgB,CAAA,CAC7E,EAAA,CAAA,EACN,kBAAC,GAAD;MAAQ,SAAQ;MAAQ,MAAK;MAAO,SAAS;MAAS,cAAW;gBAC/D,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;KAAoC;KAAe,CAAA;IAGjE,KAAU,kBAAC,OAAD;KAAK,WAAU;eAA2C;KAAa,CAAA;IAC9E;KACF;MAzCU;;AAsEpB,SAAgB,EAAgB,EAC9B,iBAAc,QACd,iBAAc,UACd,aACA,kBAAe,IACf,cAAW,IACX,WACA,aACA,mBAAgB,aACO;AACvB,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD;GAAQ,SAAQ;GAAU,SAAS;GAAU,UAAU;aACpD;GACM,CAAA,EACT,kBAAC,GAAD;GACE,MAAM,IAAS,WAAW;GAC1B,MAAM;GACN,SAAS;GACT,UAAU,KAAY;GACtB,SAAS;aALX,CAOG,KAAgB,kBAAC,GAAD,EAAS,WAAU,6BAA8B,CAAA,EACjE,EACM;KACL;;;AAkBV,SAAgB,EAAY,EAAE,UAAO,gBAAa,aAAU,gBAA+B;AACzF,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,aAAa,EAAU;YAA1C,EACI,KAAS,MACT,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,KAAS,kBAAC,MAAD;IAAI,WAAU;cAAyC;IAAW,CAAA,EAC3E,KAAe,kBAAC,KAAD;IAAG,WAAU;cAA+B;IAAgB,CAAA,CACxE;MAER,kBAAC,OAAD;GAAK,WAAU;GAAa;GAAe,CAAA,CACvC;;;AAgBV,SAAgB,EAAQ,EAAE,aAAU,UAAO,GAAG,gBAA2B;AAMvE,QAAO,kBAAC,OAAD;EAAK,WAAW,EAAG,cALR;GAChB,GAAG;GACH,GAAG;GACH,GAAG;GACJ,CACiD,IAAO,EAAU;EAAG;EAAe,CAAA"}
@@ -1,57 +1,60 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { Badge as t, Card as n, CardContent as r, CardHeader as i, CardTitle as a, Skeleton as o } from "./ui.js";
3
- import { formatChange as s, formatCurrency as c, formatNumber as l } from "../utils/formatters.js";
4
- import { Minus as u, TrendingDown as d, TrendingUp as f } from "lucide-react";
5
- import { Link as p } from "react-router-dom";
6
- import { jsx as m, jsxs as h } from "react/jsx-runtime";
2
+ import { Badge as t, Skeleton as n } from "./ui.js";
3
+ import { formatChange as r, formatCurrency as i, formatNumber as a } from "../utils/formatters.js";
4
+ import { Minus as o, TrendingDown as s, TrendingUp as c } from "lucide-react";
5
+ import { Link as l } from "react-router-dom";
6
+ import { jsx as u, jsxs as d } from "react/jsx-runtime";
7
+ import { GlassCard as f } from "@burdenoff/fe-libs/ui";
7
8
  //#region src/admin/components/MetricCard.tsx
8
- function g({ title: g, value: _, change: v, changeType: y = "number", trend: b, changeIsPositive: x, icon: S, subtitle: C, linkTo: w, priority: T, loading: E, className: D }) {
9
- let O = x ?? b === "up", k = typeof _ == "number" ? y === "currency" ? c(_) : l(_) : _, A = v === void 0 ? null : s(v, y === "percentage"), j = b === "up" ? f : b === "down" ? d : u, M = /* @__PURE__ */ h(n, {
10
- className: e("transition-shadow hover:shadow-md", T && {
9
+ function p({ title: p, value: m, change: h, changeType: g = "number", trend: _, changeIsPositive: v, icon: y, subtitle: b, linkTo: x, priority: S, loading: C, hero: w = !1, className: T }) {
10
+ let E = v ?? _ === "up", D = typeof m == "number" ? g === "currency" ? i(m) : a(m) : m, O = h === void 0 ? null : r(h, g === "percentage"), k = _ === "up" ? c : _ === "down" ? s : o, A = /* @__PURE__ */ d(f, {
11
+ treatment: w ? "glass" : "metric",
12
+ glow: w,
13
+ className: e("h-full p-6", S && {
11
14
  normal: "",
12
- high: "border-l-4 border-l-warning",
13
- critical: "border-l-4 border-l-destructive"
14
- }[T], D),
15
- children: [/* @__PURE__ */ h(i, {
15
+ high: "border-l-4 border-l-status-warning-border",
16
+ critical: "border-l-4 border-l-status-error-border"
17
+ }[S], T),
18
+ children: [/* @__PURE__ */ d("div", {
16
19
  className: "flex flex-row items-center justify-between pb-2",
17
- children: [/* @__PURE__ */ m(a, {
20
+ children: [/* @__PURE__ */ u("span", {
18
21
  className: "text-sm font-medium text-text-secondary",
19
- children: g
20
- }), S && /* @__PURE__ */ m("div", {
22
+ children: p
23
+ }), y && /* @__PURE__ */ u("div", {
21
24
  className: "text-text-secondary",
22
- children: S
25
+ children: y
23
26
  })]
24
- }), /* @__PURE__ */ m(r, { children: E ? /* @__PURE__ */ h("div", {
27
+ }), C ? /* @__PURE__ */ d("div", {
25
28
  className: "space-y-2",
26
- children: [/* @__PURE__ */ m(o, { className: "h-8 w-24" }), /* @__PURE__ */ m(o, { className: "h-4 w-16" })]
27
- }) : /* @__PURE__ */ h("div", { children: [/* @__PURE__ */ h("div", {
29
+ children: [/* @__PURE__ */ u(n, { className: "h-8 w-24" }), /* @__PURE__ */ u(n, { className: "h-4 w-16" })]
30
+ }) : /* @__PURE__ */ d("div", { children: [/* @__PURE__ */ d("div", {
28
31
  className: "flex items-baseline justify-between",
29
- children: [/* @__PURE__ */ m("div", {
30
- className: "text-3xl font-bold text-text-primary",
31
- children: k
32
- }), A !== null && /* @__PURE__ */ h(t, {
33
- variant: O ? "success" : "destructive",
34
- className: "flex items-center gap-1",
35
- children: [/* @__PURE__ */ m(j, { className: "h-3 w-3" }), A]
32
+ children: [/* @__PURE__ */ u("div", {
33
+ className: "text-3xl font-bold tabular-nums text-text-primary",
34
+ children: D
35
+ }), O !== null && /* @__PURE__ */ d(t, {
36
+ variant: E ? "success" : "destructive",
37
+ className: "flex items-center gap-1 tabular-nums",
38
+ children: [/* @__PURE__ */ u(k, { className: "h-3 w-3" }), O]
36
39
  })]
37
- }), C && /* @__PURE__ */ m("p", {
40
+ }), b && /* @__PURE__ */ u("p", {
38
41
  className: "mt-1 text-xs text-text-secondary",
39
- children: C
40
- })] }) })]
42
+ children: b
43
+ })] })]
41
44
  });
42
- return w ? /* @__PURE__ */ m(p, {
43
- to: w,
44
- className: "block",
45
- children: M
46
- }) : M;
45
+ return x ? /* @__PURE__ */ u(l, {
46
+ to: x,
47
+ className: "block rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2",
48
+ children: A
49
+ }) : A;
47
50
  }
48
- function _({ children: t, className: n }) {
49
- return /* @__PURE__ */ m("div", {
51
+ function m({ children: t, className: n }) {
52
+ return /* @__PURE__ */ u("div", {
50
53
  className: e("grid gap-4", "grid-cols-1 sm:grid-cols-2 xl:grid-cols-4", n),
51
54
  children: t
52
55
  });
53
56
  }
54
57
  //#endregion
55
- export { g as MetricCard, _ as MetricsGrid };
58
+ export { p as MetricCard, m as MetricsGrid };
56
59
 
57
60
  //# sourceMappingURL=MetricCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MetricCard.js","names":[],"sources":["../../../src/admin/components/MetricCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { Card, CardHeader, CardTitle, CardContent, Badge, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatNumber, formatCurrency, formatChange } from '../utils/formatters';\n\nexport interface MetricCardProps {\n /** Card title */\n title: string;\n /** Metric value (number or formatted string) */\n value: number | string;\n /** Change from previous period */\n change?: number;\n /** Type of change display */\n changeType?: 'number' | 'percentage' | 'currency';\n /** Trend direction */\n trend?: 'up' | 'down' | 'neutral';\n /** Whether change is positive or negative for styling */\n changeIsPositive?: boolean;\n /** Icon to display */\n icon?: ReactNode;\n /** Subtitle text */\n subtitle?: string;\n /** Link to navigate to on click */\n linkTo?: string;\n /** Priority indicator */\n priority?: 'normal' | 'high' | 'critical';\n /** Loading state */\n loading?: boolean;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Metric card component for dashboard\n */\nexport function MetricCard({\n title,\n value,\n change,\n changeType = 'number',\n trend,\n changeIsPositive,\n icon,\n subtitle,\n linkTo,\n priority,\n loading,\n className,\n}: MetricCardProps) {\n // Determine if change is positive based on trend or explicit prop\n const isPositive = changeIsPositive ?? trend === 'up';\n\n // Format the value\n const formattedValue =\n typeof value === 'number'\n ? changeType === 'currency'\n ? formatCurrency(value)\n : formatNumber(value)\n : value;\n\n // Format the change\n const formattedChange =\n change !== undefined ? formatChange(change, changeType === 'percentage') : null;\n\n // Get trend icon\n const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus;\n\n // Priority border classes\n const priorityClasses = {\n normal: '',\n high: 'border-l-4 border-l-warning',\n critical: 'border-l-4 border-l-destructive',\n };\n\n const cardContent = (\n <Card\n className={cn(\n 'transition-shadow hover:shadow-md',\n priority && priorityClasses[priority],\n className\n )}\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"text-sm font-medium text-text-secondary\">{title}</CardTitle>\n {icon && <div className=\"text-text-secondary\">{icon}</div>}\n </CardHeader>\n <CardContent>\n {loading ? (\n <div className=\"space-y-2\">\n <Skeleton className=\"h-8 w-24\" />\n <Skeleton className=\"h-4 w-16\" />\n </div>\n ) : (\n <div>\n <div className=\"flex items-baseline justify-between\">\n <div className=\"text-3xl font-bold text-text-primary\">{formattedValue}</div>\n {formattedChange !== null && (\n <Badge\n variant={isPositive ? 'success' : 'destructive'}\n className=\"flex items-center gap-1\"\n >\n <TrendIcon className=\"h-3 w-3\" />\n {formattedChange}\n </Badge>\n )}\n </div>\n {subtitle && <p className=\"mt-1 text-xs text-text-secondary\">{subtitle}</p>}\n </div>\n )}\n </CardContent>\n </Card>\n );\n\n if (linkTo) {\n return (\n <Link to={linkTo} className=\"block\">\n {cardContent}\n </Link>\n );\n }\n\n return cardContent;\n}\n\n/**\n * Grid container for metric cards\n */\nexport function MetricsGrid({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <div className={cn('grid gap-4', 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4', className)}>\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAqCA,SAAgB,EAAW,EACzB,UACA,UACA,WACA,gBAAa,UACb,UACA,qBACA,SACA,aACA,WACA,aACA,YACA,gBACkB;CAElB,IAAM,IAAa,KAAoB,MAAU,MAG3C,IACJ,OAAO,KAAU,WACb,MAAe,aACb,EAAe,EAAM,GACrB,EAAa,EAAM,GACrB,GAGA,IACJ,MAAW,KAAA,IAAgE,OAApD,EAAa,GAAQ,MAAe,aAAa,EAGpE,IAAY,MAAU,OAAO,IAAa,MAAU,SAAS,IAAe,GAS5E,IACJ,kBAAC,GAAD;EACE,WAAW,EACT,qCACA,KAVkB;GACtB,QAAQ;GACR,MAAM;GACN,UAAU;GACX,CAMiC,IAC5B,EACD;YALH,CAOE,kBAAC,GAAD;GAAY,WAAU;aAAtB,CACE,kBAAC,GAAD;IAAW,WAAU;cAA2C;IAAkB,CAAA,EACjF,KAAQ,kBAAC,OAAD;IAAK,WAAU;cAAuB;IAAW,CAAA,CAC/C;MACb,kBAAC,GAAD,EAAA,UACG,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,EACjC,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,CAC7B;OAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAwC;IAAqB,CAAA,EAC3E,MAAoB,QACnB,kBAAC,GAAD;IACE,SAAS,IAAa,YAAY;IAClC,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EACK;MAEN;MACL,KAAY,kBAAC,KAAD;GAAG,WAAU;aAAoC;GAAa,CAAA,CACvE,EAAA,CAAA,EAEI,CAAA,CACT;;AAWT,QARI,IAEA,kBAAC,GAAD;EAAM,IAAI;EAAQ,WAAU;YACzB;EACI,CAAA,GAIJ;;AAMT,SAAgB,EAAY,EAAE,aAAU,gBAA0D;AAChG,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,cAAc,6CAA6C,EAAU;EACrF;EACG,CAAA"}
1
+ {"version":3,"file":"MetricCard.js","names":[],"sources":["../../../src/admin/components/MetricCard.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { Link } from 'react-router-dom';\nimport { TrendingUp, TrendingDown, Minus } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport { Badge, Skeleton } from './ui';\nimport { cn } from '../utils/cn';\nimport { formatNumber, formatCurrency, formatChange } from '../utils/formatters';\n\nexport interface MetricCardProps {\n /** Card title */\n title: string;\n /** Metric value (number or formatted string) */\n value: number | string;\n /** Change from previous period */\n change?: number;\n /** Type of change display */\n changeType?: 'number' | 'percentage' | 'currency';\n /** Trend direction */\n trend?: 'up' | 'down' | 'neutral';\n /** Whether change is positive or negative for styling */\n changeIsPositive?: boolean;\n /** Icon to display */\n icon?: ReactNode;\n /** Subtitle text */\n subtitle?: string;\n /** Link to navigate to on click */\n linkTo?: string;\n /** Priority indicator */\n priority?: 'normal' | 'high' | 'critical';\n /** Loading state */\n loading?: boolean;\n /**\n * Hero metric — uses the glass treatment with an accent glow (Boff UI 2.0).\n * Reserve for the single most important metric per group.\n */\n hero?: boolean;\n /** Additional class name */\n className?: string;\n}\n\n/**\n * Metric card component for dashboard\n */\nexport function MetricCard({\n title,\n value,\n change,\n changeType = 'number',\n trend,\n changeIsPositive,\n icon,\n subtitle,\n linkTo,\n priority,\n loading,\n hero = false,\n className,\n}: MetricCardProps) {\n // Determine if change is positive based on trend or explicit prop\n const isPositive = changeIsPositive ?? trend === 'up';\n\n // Format the value\n const formattedValue =\n typeof value === 'number'\n ? changeType === 'currency'\n ? formatCurrency(value)\n : formatNumber(value)\n : value;\n\n // Format the change\n const formattedChange =\n change !== undefined ? formatChange(change, changeType === 'percentage') : null;\n\n // Get trend icon\n const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus;\n\n // Priority accent (left rail) — semantic tokens only.\n const priorityClasses = {\n normal: '',\n high: 'border-l-4 border-l-status-warning-border',\n critical: 'border-l-4 border-l-status-error-border',\n };\n\n const cardContent = (\n <GlassCard\n treatment={hero ? 'glass' : 'metric'}\n glow={hero}\n className={cn('h-full p-6', priority && priorityClasses[priority], className)}\n >\n <div className=\"flex flex-row items-center justify-between pb-2\">\n <span className=\"text-sm font-medium text-text-secondary\">{title}</span>\n {icon && <div className=\"text-text-secondary\">{icon}</div>}\n </div>\n {loading ? (\n <div className=\"space-y-2\">\n <Skeleton className=\"h-8 w-24\" />\n <Skeleton className=\"h-4 w-16\" />\n </div>\n ) : (\n <div>\n <div className=\"flex items-baseline justify-between\">\n <div className=\"text-3xl font-bold tabular-nums text-text-primary\">\n {formattedValue}\n </div>\n {formattedChange !== null && (\n <Badge\n variant={isPositive ? 'success' : 'destructive'}\n className=\"flex items-center gap-1 tabular-nums\"\n >\n <TrendIcon className=\"h-3 w-3\" />\n {formattedChange}\n </Badge>\n )}\n </div>\n {subtitle && <p className=\"mt-1 text-xs text-text-secondary\">{subtitle}</p>}\n </div>\n )}\n </GlassCard>\n );\n\n if (linkTo) {\n return (\n <Link\n to={linkTo}\n className=\"block rounded-card focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] focus-visible:ring-offset-2\"\n >\n {cardContent}\n </Link>\n );\n }\n\n return cardContent;\n}\n\n/**\n * Grid container for metric cards\n */\nexport function MetricsGrid({ children, className }: { children: ReactNode; className?: string }) {\n return (\n <div className={cn('grid gap-4', 'grid-cols-1 sm:grid-cols-2 xl:grid-cols-4', className)}>\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;AA2CA,SAAgB,EAAW,EACzB,UACA,UACA,WACA,gBAAa,UACb,UACA,qBACA,SACA,aACA,WACA,aACA,YACA,UAAO,IACP,gBACkB;CAElB,IAAM,IAAa,KAAoB,MAAU,MAG3C,IACJ,OAAO,KAAU,WACb,MAAe,aACb,EAAe,EAAM,GACrB,EAAa,EAAM,GACrB,GAGA,IACJ,MAAW,KAAA,IAAgE,OAApD,EAAa,GAAQ,MAAe,aAAa,EAGpE,IAAY,MAAU,OAAO,IAAa,MAAU,SAAS,IAAe,GAS5E,IACJ,kBAAC,GAAD;EACE,WAAW,IAAO,UAAU;EAC5B,MAAM;EACN,WAAW,EAAG,cAAc,KAVR;GACtB,QAAQ;GACR,MAAM;GACN,UAAU;GACX,CAM2D,IAAW,EAAU;YAH/E,CAKE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,QAAD;IAAM,WAAU;cAA2C;IAAa,CAAA,EACvE,KAAQ,kBAAC,OAAD;IAAK,WAAU;cAAuB;IAAW,CAAA,CACtD;MACL,IACC,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,EACjC,kBAAC,GAAD,EAAU,WAAU,YAAa,CAAA,CAC7B;OAEN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACZ;IACG,CAAA,EACL,MAAoB,QACnB,kBAAC,GAAD;IACE,SAAS,IAAa,YAAY;IAClC,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA,EAChC,EACK;MAEN;MACL,KAAY,kBAAC,KAAD;GAAG,WAAU;aAAoC;GAAa,CAAA,CACvE,EAAA,CAAA,CAEE;;AAcd,QAXI,IAEA,kBAAC,GAAD;EACE,IAAI;EACJ,WAAU;YAET;EACI,CAAA,GAIJ;;AAMT,SAAgB,EAAY,EAAE,aAAU,gBAA0D;AAChG,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,cAAc,6CAA6C,EAAU;EACrF;EACG,CAAA"}
@@ -1,31 +1,37 @@
1
1
  import { cn as e } from "../utils/cn.js";
2
- import { jsx as t, jsxs as n } from "react/jsx-runtime";
2
+ import { Fragment as t, jsx as n, jsxs as r } from "react/jsx-runtime";
3
+ import { AuroraBackground as i, GradientText as a } from "@burdenoff/fe-libs/ui";
3
4
  //#region src/admin/components/PageLayout.tsx
4
- function r({ title: r, description: i, actions: a, children: o, className: s }) {
5
- return /* @__PURE__ */ n("div", {
6
- className: e("flex h-full flex-col", s),
7
- children: [/* @__PURE__ */ n("header", {
8
- className: "flex flex-col gap-4 border-b border-border-subtle bg-bg-surface px-6 py-4 sm:flex-row sm:items-center sm:justify-between",
9
- children: [/* @__PURE__ */ n("div", {
10
- className: "min-w-0 flex-1",
11
- children: [/* @__PURE__ */ t("h1", {
12
- className: "text-2xl font-bold tracking-tight text-text-primary sm:text-3xl",
13
- children: r
14
- }), i && /* @__PURE__ */ t("p", {
15
- className: "mt-1 text-sm text-text-secondary sm:text-base",
16
- children: i
17
- })]
18
- }), a && /* @__PURE__ */ t("div", {
19
- className: "flex flex-shrink-0 flex-wrap items-center gap-2",
20
- children: a
21
- })]
22
- }), /* @__PURE__ */ t("div", {
5
+ function o({ title: o, description: s, actions: c, children: l, className: u, gradientWord: d, aurora: f = !1 }) {
6
+ let p = o, m = null;
7
+ return d && o.endsWith(d) && o.length > d.length ? (p = o.slice(0, o.length - d.length), m = d) : d && o === d && (p = "", m = d), /* @__PURE__ */ r("div", {
8
+ className: e("flex h-full flex-col", u),
9
+ children: [/* @__PURE__ */ r("header", {
10
+ className: e("relative flex flex-col gap-4 border-b border-border-seam bg-bg-surface px-6 py-4 sm:flex-row sm:items-center sm:justify-between", f && "overflow-hidden"),
11
+ children: [
12
+ f && /* @__PURE__ */ n(i, { intensity: "subtle" }),
13
+ /* @__PURE__ */ r("div", {
14
+ className: "min-w-0 flex-1",
15
+ children: [/* @__PURE__ */ n("h1", {
16
+ className: "text-2xl font-bold tracking-tight text-text-primary sm:text-3xl",
17
+ children: m === null ? o : /* @__PURE__ */ r(t, { children: [p, /* @__PURE__ */ n(a, { children: m })] })
18
+ }), s && /* @__PURE__ */ n("p", {
19
+ className: "mt-1 text-sm text-text-secondary sm:text-base",
20
+ children: s
21
+ })]
22
+ }),
23
+ c && /* @__PURE__ */ n("div", {
24
+ className: "flex flex-shrink-0 flex-wrap items-center gap-2",
25
+ children: c
26
+ })
27
+ ]
28
+ }), /* @__PURE__ */ n("div", {
23
29
  className: "flex-1 overflow-auto p-6",
24
- children: o
30
+ children: l
25
31
  })]
26
32
  });
27
33
  }
28
34
  //#endregion
29
- export { r as PageLayout };
35
+ export { o as PageLayout };
30
36
 
31
37
  //# sourceMappingURL=PageLayout.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/admin/components/PageLayout.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n /** Page title */\n title: string;\n /** Optional description */\n description?: string;\n /** Actions to display in the header (buttons, etc.) */\n actions?: ReactNode;\n /** Page content */\n children: ReactNode;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Standard page layout component with title, description, and content area\n */\nexport function PageLayout({ title, description, actions, children, className }: PageLayoutProps) {\n return (\n <div className={cn('flex h-full flex-col', className)}>\n {/* Page Header */}\n <header className=\"flex flex-col gap-4 border-b border-border-subtle bg-bg-surface px-6 py-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-bold tracking-tight text-text-primary sm:text-3xl\">{title}</h1>\n {description && (\n <p className=\"mt-1 text-sm text-text-secondary sm:text-base\">{description}</p>\n )}\n </div>\n {actions && (\n <div className=\"flex flex-shrink-0 flex-wrap items-center gap-2\">{actions}</div>\n )}\n </header>\n\n {/* Page Content — must not use a <main> landmark here because the\n host shell (AppShellLayout) already renders one; nesting <main>\n elements is invalid HTML and an a11y violation. */}\n <div className=\"flex-1 overflow-auto p-6\">{children}</div>\n </div>\n );\n}\n\ninterface PageSectionProps {\n /** Section title */\n title?: string;\n /** Optional description */\n description?: string;\n /** Section content */\n children: ReactNode;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Section within a page\n */\nexport function PageSection({ title, description, children, className }: PageSectionProps) {\n return (\n <section className={cn('space-y-4', className)}>\n {(title || description) && (\n <div>\n {title && <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>}\n {description && <p className=\"text-sm text-text-secondary\">{description}</p>}\n </div>\n )}\n {children}\n </section>\n );\n}\n"],"mappings":";;;AAmBA,SAAgB,EAAW,EAAE,UAAO,gBAAa,YAAS,aAAU,gBAA8B;AAChG,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,wBAAwB,EAAU;YAArD,CAEE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAAmE;KAAW,CAAA,EAC3F,KACC,kBAAC,KAAD;KAAG,WAAU;eAAiD;KAAgB,CAAA,CAE5E;OACL,KACC,kBAAC,OAAD;IAAK,WAAU;cAAmD;IAAc,CAAA,CAE3E;MAKT,kBAAC,OAAD;GAAK,WAAU;GAA4B;GAAe,CAAA,CACtD"}
1
+ {"version":3,"file":"PageLayout.js","names":[],"sources":["../../../src/admin/components/PageLayout.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { AuroraBackground, GradientText } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\n\ninterface PageLayoutProps {\n /** Page title */\n title: string;\n /** Optional description */\n description?: string;\n /** Actions to display in the header (buttons, etc.) */\n actions?: ReactNode;\n /** Page content */\n children: ReactNode;\n /** Additional class names */\n className?: string;\n /**\n * When set, the trailing words of the title are clipped with a brand\n * gradient (Boff UI 2.0 hero treatment). The leading text stays solid.\n * Provide the exact substring that appears at the end of `title`.\n */\n gradientWord?: string;\n /**\n * Render the living aurora backdrop behind the page header (Boff UI 2.0).\n * Use at most ONE per MFE — set on the primary/landing page header only.\n */\n aurora?: boolean;\n}\n\n/**\n * Standard page layout component with title, description, and content area\n */\nexport function PageLayout({\n title,\n description,\n actions,\n children,\n className,\n gradientWord,\n aurora = false,\n}: PageLayoutProps) {\n // Split the title so only the trailing key noun is gradient-clipped (ONE per page).\n let leading = title;\n let gradient: string | null = null;\n if (gradientWord && title.endsWith(gradientWord) && title.length > gradientWord.length) {\n leading = title.slice(0, title.length - gradientWord.length);\n gradient = gradientWord;\n } else if (gradientWord && title === gradientWord) {\n leading = '';\n gradient = gradientWord;\n }\n\n return (\n <div className={cn('flex h-full flex-col', className)}>\n {/* Page Header */}\n <header\n className={cn(\n 'relative flex flex-col gap-4 border-b border-border-seam bg-bg-surface px-6 py-4 sm:flex-row sm:items-center sm:justify-between',\n aurora && 'overflow-hidden'\n )}\n >\n {aurora && <AuroraBackground intensity=\"subtle\" />}\n <div className=\"min-w-0 flex-1\">\n <h1 className=\"text-2xl font-bold tracking-tight text-text-primary sm:text-3xl\">\n {gradient !== null ? (\n <>\n {leading}\n <GradientText>{gradient}</GradientText>\n </>\n ) : (\n title\n )}\n </h1>\n {description && (\n <p className=\"mt-1 text-sm text-text-secondary sm:text-base\">{description}</p>\n )}\n </div>\n {actions && (\n <div className=\"flex flex-shrink-0 flex-wrap items-center gap-2\">{actions}</div>\n )}\n </header>\n\n {/* Page Content — must not use a <main> landmark here because the\n host shell (AppShellLayout) already renders one; nesting <main>\n elements is invalid HTML and an a11y violation. */}\n <div className=\"flex-1 overflow-auto p-6\">{children}</div>\n </div>\n );\n}\n\ninterface PageSectionProps {\n /** Section title */\n title?: string;\n /** Optional description */\n description?: string;\n /** Section content */\n children: ReactNode;\n /** Additional class names */\n className?: string;\n}\n\n/**\n * Section within a page\n */\nexport function PageSection({ title, description, children, className }: PageSectionProps) {\n return (\n <section className={cn('space-y-4', className)}>\n {(title || description) && (\n <div>\n {title && <h2 className=\"text-lg font-semibold text-text-primary\">{title}</h2>}\n {description && <p className=\"text-sm text-text-secondary\">{description}</p>}\n </div>\n )}\n {children}\n </section>\n );\n}\n"],"mappings":";;;;AA+BA,SAAgB,EAAW,EACzB,UACA,gBACA,YACA,aACA,cACA,iBACA,YAAS,MACS;CAElB,IAAI,IAAU,GACV,IAA0B;AAS9B,QARI,KAAgB,EAAM,SAAS,EAAa,IAAI,EAAM,SAAS,EAAa,UAC9E,IAAU,EAAM,MAAM,GAAG,EAAM,SAAS,EAAa,OAAO,EAC5D,IAAW,KACF,KAAgB,MAAU,MACnC,IAAU,IACV,IAAW,IAIX,kBAAC,OAAD;EAAK,WAAW,EAAG,wBAAwB,EAAU;YAArD,CAEE,kBAAC,UAAD;GACE,WAAW,EACT,mIACA,KAAU,kBACX;aAJH;IAMG,KAAU,kBAAC,GAAD,EAAkB,WAAU,UAAW,CAAA;IAClD,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,MAAa,OAMZ,IALA,kBAAA,GAAA,EAAA,UAAA,CACG,GACD,kBAAC,GAAD,EAAA,UAAe,GAAwB,CAAA,CACtC,EAAA,CAAA;MAIF,CAAA,EACJ,KACC,kBAAC,KAAD;MAAG,WAAU;gBAAiD;MAAgB,CAAA,CAE5E;;IACL,KACC,kBAAC,OAAD;KAAK,WAAU;eAAmD;KAAc,CAAA;IAE3E;MAKT,kBAAC,OAAD;GAAK,WAAU;GAA4B;GAAe,CAAA,CACtD"}
@@ -234,7 +234,7 @@ function E({ tenantId: E, canManage: D }) {
234
234
  enabled: !e.enabled
235
235
  })),
236
236
  className: `relative inline-flex h-5 w-9 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] focus:ring-offset-2 ${F.enabled ? "bg-action-primary-bg" : "bg-bg-sunken"}`,
237
- children: /* @__PURE__ */ S("span", { className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-white shadow-lg transition-transform ${F.enabled ? "translate-x-4" : "translate-x-0"}` })
237
+ children: /* @__PURE__ */ S("span", { className: `pointer-events-none inline-block h-4 w-4 transform rounded-full bg-bg-surface shadow-lg transition-transform ${F.enabled ? "translate-x-4" : "translate-x-0"}` })
238
238
  }), /* @__PURE__ */ S("span", {
239
239
  className: "text-sm",
240
240
  children: k("admin.tenantDetail.sso.enabledLabel", "Enable SSO for this tenant")