@burdenoff/microfe-workspaces 2026.917.1 → 2026.1010.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 (165) hide show
  1. package/dist/WorkspacesRoot.js +7 -9
  2. package/dist/WorkspacesRoutes.js +2 -4
  3. package/dist/components/CreateProjectDialog.js +0 -2
  4. package/dist/components/CreateWorkspaceDialog.js +0 -2
  5. package/dist/components/InviteMemberDialog.js +0 -2
  6. package/dist/components/MemberListItem.js +0 -2
  7. package/dist/components/ProjectCard.js +0 -2
  8. package/dist/components/ProjectMemberPickerDialog.js +0 -2
  9. package/dist/components/RenameProjectDialog.js +0 -2
  10. package/dist/components/WorkspaceCard.js +0 -2
  11. package/dist/components/WorkspaceContextPathSync.js +0 -2
  12. package/dist/components/WorkspacesErrorBoundary.js +0 -2
  13. package/dist/components/tags/ProjectTagsWidget.js +0 -2
  14. package/dist/constants/permissions.js +1 -3
  15. package/dist/generated/global-operations.js +0 -2
  16. package/dist/generated/wspace-operations.js +0 -2
  17. package/dist/hooks/useMemberMutations.js +0 -2
  18. package/dist/hooks/useOrgsWithPermission.js +0 -2
  19. package/dist/hooks/usePendingInvitations.js +0 -2
  20. package/dist/hooks/useProjectMemberMutations.js +0 -2
  21. package/dist/hooks/useProjectMembers.js +0 -2
  22. package/dist/hooks/useProjectMutations.js +0 -2
  23. package/dist/hooks/useProjects.js +0 -2
  24. package/dist/hooks/useScopedNavigation.js +0 -2
  25. package/dist/hooks/useTenants.js +0 -2
  26. package/dist/hooks/useTr.js +0 -2
  27. package/dist/hooks/useWorkspaceActivity.js +0 -2
  28. package/dist/hooks/useWorkspaceMembers.js +0 -2
  29. package/dist/hooks/useWorkspaceMutations.js +0 -2
  30. package/dist/hooks/useWorkspacePermissions.js +0 -2
  31. package/dist/hooks/useWorkspaceRoles.js +0 -2
  32. package/dist/hooks/useWorkspaces.js +0 -2
  33. package/dist/hooks/useWorkspacesEventEmitter.js +0 -2
  34. package/dist/hooks/useWorkspacesPageView.js +0 -2
  35. package/dist/pages/ActivityListPage.js +0 -2
  36. package/dist/pages/CreateWorkspacePage.js +0 -2
  37. package/dist/pages/InviteAcceptancePage.js +0 -2
  38. package/dist/pages/MembersListPage.js +0 -2
  39. package/dist/pages/PendingInvitesPage.js +0 -2
  40. package/dist/pages/ProjectDetailPage.js +0 -2
  41. package/dist/pages/ProjectMembersPage.js +0 -2
  42. package/dist/pages/ProjectsListPage.js +0 -2
  43. package/dist/pages/WorkspaceDetailPage.js +0 -2
  44. package/dist/pages/WorkspaceInvitePage.js +0 -2
  45. package/dist/pages/WorkspaceSettingsPage.js +0 -2
  46. package/dist/pages/WorkspacesListPage.js +0 -2
  47. package/dist/pages/dashboard/DashboardCustomizer.js +0 -2
  48. package/dist/pages/dashboard/DashboardGrid.js +0 -2
  49. package/dist/pages/dashboard/DashboardPage.js +0 -2
  50. package/dist/pages/dashboard/WidgetWrapper.js +0 -2
  51. package/dist/pages/dashboard/config.js +0 -2
  52. package/dist/pages/dashboard/hooks/useDashboardData.js +17 -13
  53. package/dist/pages/dashboard/hooks/useDashboardPreferences.js +0 -2
  54. package/dist/pages/dashboard/reorder.js +0 -2
  55. package/dist/pages/dashboard/widgets/AgentsWidget.js +0 -2
  56. package/dist/pages/dashboard/widgets/GroupsWidget.js +0 -2
  57. package/dist/pages/dashboard/widgets/MembersWidget.js +0 -2
  58. package/dist/pages/dashboard/widgets/ProjectsWidget.js +0 -2
  59. package/dist/pages/dashboard/widgets/QuickActionsWidget.js +0 -2
  60. package/dist/pages/dashboard/widgets/RecentActivityWidget.js +0 -2
  61. package/dist/pages/dashboard/widgets/SessionsWidget.js +0 -2
  62. package/dist/pages/dashboard/widgets/StorageWidget.js +0 -2
  63. package/dist/pages/dashboard/widgets/TagCloudWidget.js +0 -2
  64. package/dist/pages/dashboard/widgets/TipsWidget.js +0 -2
  65. package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +0 -2
  66. package/dist/pages/dashboard/widgets/VibesWidget.js +0 -2
  67. package/dist/pages/dashboard/widgets/WelcomeWidget.js +0 -2
  68. package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js +0 -2
  69. package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +0 -2
  70. package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js +0 -2
  71. package/dist/pages/dashboard/widgets/WorkspacesWidget.js +0 -2
  72. package/dist/providers/WorkspacesProvider.js +0 -2
  73. package/dist/store/workspacesStore.js +0 -2
  74. package/dist/translations/en.js +0 -2
  75. package/dist/utils/formatters.js +0 -2
  76. package/dist/utils/i18n.js +0 -2
  77. package/dist/utils/invitationDisplay.js +0 -2
  78. package/dist/utils/projectTags.js +0 -2
  79. package/dist/utils/slug.js +0 -2
  80. package/dist/utils/telemetryError.js +0 -2
  81. package/dist/utils/tokenHelpers.js +0 -2
  82. package/dist/utils/workspaceBulkInviteCsv.js +0 -2
  83. package/package.json +3 -2
  84. package/dist/WorkspacesRoot.js.map +0 -1
  85. package/dist/WorkspacesRoutes.js.map +0 -1
  86. package/dist/components/CreateProjectDialog.js.map +0 -1
  87. package/dist/components/CreateWorkspaceDialog.js.map +0 -1
  88. package/dist/components/InviteMemberDialog.js.map +0 -1
  89. package/dist/components/MemberListItem.js.map +0 -1
  90. package/dist/components/ProjectCard.js.map +0 -1
  91. package/dist/components/ProjectMemberPickerDialog.js.map +0 -1
  92. package/dist/components/RenameProjectDialog.js.map +0 -1
  93. package/dist/components/WorkspaceCard.js.map +0 -1
  94. package/dist/components/WorkspaceContextPathSync.js.map +0 -1
  95. package/dist/components/WorkspacesErrorBoundary.js.map +0 -1
  96. package/dist/components/tags/ProjectTagsWidget.js.map +0 -1
  97. package/dist/constants/permissions.js.map +0 -1
  98. package/dist/generated/global-operations.js.map +0 -1
  99. package/dist/generated/wspace-operations.js.map +0 -1
  100. package/dist/hooks/useMemberMutations.js.map +0 -1
  101. package/dist/hooks/useOrgsWithPermission.js.map +0 -1
  102. package/dist/hooks/usePendingInvitations.js.map +0 -1
  103. package/dist/hooks/useProjectMemberMutations.js.map +0 -1
  104. package/dist/hooks/useProjectMembers.js.map +0 -1
  105. package/dist/hooks/useProjectMutations.js.map +0 -1
  106. package/dist/hooks/useProjects.js.map +0 -1
  107. package/dist/hooks/useScopedNavigation.js.map +0 -1
  108. package/dist/hooks/useTenants.js.map +0 -1
  109. package/dist/hooks/useTr.js.map +0 -1
  110. package/dist/hooks/useWorkspaceActivity.js.map +0 -1
  111. package/dist/hooks/useWorkspaceMembers.js.map +0 -1
  112. package/dist/hooks/useWorkspaceMutations.js.map +0 -1
  113. package/dist/hooks/useWorkspacePermissions.js.map +0 -1
  114. package/dist/hooks/useWorkspaceRoles.js.map +0 -1
  115. package/dist/hooks/useWorkspaces.js.map +0 -1
  116. package/dist/hooks/useWorkspacesEventEmitter.js.map +0 -1
  117. package/dist/hooks/useWorkspacesPageView.js.map +0 -1
  118. package/dist/pages/ActivityListPage.js.map +0 -1
  119. package/dist/pages/CreateWorkspacePage.js.map +0 -1
  120. package/dist/pages/InviteAcceptancePage.js.map +0 -1
  121. package/dist/pages/MembersListPage.js.map +0 -1
  122. package/dist/pages/PendingInvitesPage.js.map +0 -1
  123. package/dist/pages/ProjectDetailPage.js.map +0 -1
  124. package/dist/pages/ProjectMembersPage.js.map +0 -1
  125. package/dist/pages/ProjectsListPage.js.map +0 -1
  126. package/dist/pages/WorkspaceDetailPage.js.map +0 -1
  127. package/dist/pages/WorkspaceInvitePage.js.map +0 -1
  128. package/dist/pages/WorkspaceSettingsPage.js.map +0 -1
  129. package/dist/pages/WorkspacesListPage.js.map +0 -1
  130. package/dist/pages/dashboard/DashboardCustomizer.js.map +0 -1
  131. package/dist/pages/dashboard/DashboardGrid.js.map +0 -1
  132. package/dist/pages/dashboard/DashboardPage.js.map +0 -1
  133. package/dist/pages/dashboard/WidgetWrapper.js.map +0 -1
  134. package/dist/pages/dashboard/config.js.map +0 -1
  135. package/dist/pages/dashboard/hooks/useDashboardData.js.map +0 -1
  136. package/dist/pages/dashboard/hooks/useDashboardPreferences.js.map +0 -1
  137. package/dist/pages/dashboard/reorder.js.map +0 -1
  138. package/dist/pages/dashboard/widgets/AgentsWidget.js.map +0 -1
  139. package/dist/pages/dashboard/widgets/GroupsWidget.js.map +0 -1
  140. package/dist/pages/dashboard/widgets/MembersWidget.js.map +0 -1
  141. package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +0 -1
  142. package/dist/pages/dashboard/widgets/QuickActionsWidget.js.map +0 -1
  143. package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +0 -1
  144. package/dist/pages/dashboard/widgets/SessionsWidget.js.map +0 -1
  145. package/dist/pages/dashboard/widgets/StorageWidget.js.map +0 -1
  146. package/dist/pages/dashboard/widgets/TagCloudWidget.js.map +0 -1
  147. package/dist/pages/dashboard/widgets/TipsWidget.js.map +0 -1
  148. package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +0 -1
  149. package/dist/pages/dashboard/widgets/VibesWidget.js.map +0 -1
  150. package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +0 -1
  151. package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js.map +0 -1
  152. package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +0 -1
  153. package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js.map +0 -1
  154. package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +0 -1
  155. package/dist/providers/WorkspacesProvider.js.map +0 -1
  156. package/dist/store/workspacesStore.js.map +0 -1
  157. package/dist/translations/en.js.map +0 -1
  158. package/dist/utils/formatters.js.map +0 -1
  159. package/dist/utils/i18n.js.map +0 -1
  160. package/dist/utils/invitationDisplay.js.map +0 -1
  161. package/dist/utils/projectTags.js.map +0 -1
  162. package/dist/utils/slug.js.map +0 -1
  163. package/dist/utils/telemetryError.js.map +0 -1
  164. package/dist/utils/tokenHelpers.js.map +0 -1
  165. package/dist/utils/workspaceBulkInviteCsv.js.map +0 -1
@@ -88,5 +88,3 @@ var i = {
88
88
  }));
89
89
  //#endregion
90
90
  export { a as useWorkspacesStore };
91
-
92
- //# sourceMappingURL=workspacesStore.js.map
@@ -904,5 +904,3 @@ var e = {
904
904
  };
905
905
  //#endregion
906
906
  export { e as enTranslations };
907
-
908
- //# sourceMappingURL=en.js.map
@@ -62,5 +62,3 @@ function d(e) {
62
62
  }
63
63
  //#endregion
64
64
  export { r as formatDate, a as formatDateTime, d as formatFileSize, l as formatProjectStatus, i as formatRelativeTime, c as formatWorkspaceStatus, u as getInitials, s as toTitleCase, o as truncate };
65
-
66
- //# sourceMappingURL=formatters.js.map
@@ -8,5 +8,3 @@ function e(e, t, n, r) {
8
8
  }
9
9
  //#endregion
10
10
  export { e as tWithFallback };
11
-
12
- //# sourceMappingURL=i18n.js.map
@@ -21,5 +21,3 @@ function n(e) {
21
21
  }
22
22
  //#endregion
23
23
  export { n as getProjectDisplayNames, t as getRoleDisplayNames, e as getWorkspaceDisplayName };
24
-
25
- //# sourceMappingURL=invitationDisplay.js.map
@@ -71,5 +71,3 @@ function a(e) {
71
71
  }
72
72
  //#endregion
73
73
  export { i as getProjectDisplayTags, a as toProjectTag };
74
-
75
- //# sourceMappingURL=projectTags.js.map
@@ -4,5 +4,3 @@ function e(e) {
4
4
  }
5
5
  //#endregion
6
6
  export { e as generateSlug };
7
-
8
- //# sourceMappingURL=slug.js.map
@@ -24,5 +24,3 @@ function r(t) {
24
24
  }
25
25
  //#endregion
26
26
  export { r as safeTelemetryError };
27
-
28
- //# sourceMappingURL=telemetryError.js.map
@@ -76,5 +76,3 @@ function c(e) {
76
76
  }
77
77
  //#endregion
78
78
  export { n as getMemberRoleClasses, c as getMemberRoleLabel, t as getProjectStatusClasses, i as getProjectStatusIconClass, s as getProjectStatusLabel, a as getWorkspaceStatusBgClass, e as getWorkspaceStatusClasses, r as getWorkspaceStatusIconClass, o as getWorkspaceStatusLabel };
79
-
80
- //# sourceMappingURL=tokenHelpers.js.map
@@ -38,5 +38,3 @@ function a() {
38
38
  }
39
39
  //#endregion
40
40
  export { a as downloadWorkspaceBulkInviteTemplate, n as parseWorkspaceBulkInviteCsv };
41
-
42
- //# sourceMappingURL=workspaceBulkInviteCsv.js.map
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-workspaces",
3
- "version": "2026.917.1",
3
+ "version": "2026.1010.1",
4
4
  "description": "Workspaces microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "dist",
8
+ "!dist/**/*.map",
8
9
  "package.json",
9
10
  "README.md"
10
11
  ],
@@ -32,7 +33,7 @@
32
33
  "test": "bash scripts/build-limits.sh vitest run",
33
34
  "test:watch": "vitest",
34
35
  "test:coverage": "bash scripts/build-limits.sh vitest run --coverage",
35
- "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run build && bun run test'",
36
+ "sanity": "bash scripts/build-limits.sh --gate bash -c 'bun run codegen && bun run lint:sanity && bun run format && bun run type:check && bun run build && bash scripts/verify-package-contents.sh && bun run test'",
36
37
  "schema:pull": "graphql-codegen --project schema-wspace && graphql-codegen --project schema-global",
37
38
  "codegen": "graphql-codegen --project wspace && graphql-codegen --project global",
38
39
  "codegen:all": "bun run schema:pull && bun run codegen",
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspacesRoot.js","names":[],"sources":["../src/WorkspacesRoot.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useEffect, useMemo } from 'react';\nimport { Building2, FolderKanban, Users, Activity } from 'lucide-react';\nimport { useFeatureFlags, FeatureFlagProvider } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { WorkspacesProvider } from './providers/WorkspacesProvider';\nimport { WorkspacesErrorBoundary } from './components/WorkspacesErrorBoundary';\nimport type { NavItem, WorkspacesRootProps } from './types';\nimport { WorkspacesRoutes } from './WorkspacesRoutes';\n\n/**\n * Root component for the Workspaces microfrontend.\n *\n * This is the main entry point that host applications import and render.\n * It sets up the necessary providers and renders the internal routing.\n * Automatically registers navigation items with the shell if registration functions are provided.\n * Nav items for feature-previewed pages (projects, members, activity) are conditionally\n * hidden based on feature flags.\n *\n * @example\n * ```tsx\n * import { WorkspacesRoot } from '@burdenoff/micro-fe/workspaces';\n *\n * function App() {\n * return (\n * <Route path=\"/workspaces/*\" element={\n * <WorkspacesRoot\n * basePath=\"/workspaces\"\n * navigate={navigate}\n * currentUser={user}\n * workspaceId=\"ws-123\"\n * tenantId=\"tenant-123\"\n * apiGatewayUrl=\"https://gw-global.local.burdenoff.com\"\n * authToken=\"token\"\n * />\n * } />\n * );\n * }\n * ```\n */\nexport const WorkspacesRoot: FC<WorkspacesRootProps> = (props) => {\n const { registerNavItems, registerFooterItems, basePath = '/' } = props;\n const { isEnabled, evaluations } = useFeatureFlags();\n\n // Helper to join paths correctly, avoiding double slashes\n const joinPath = (base: string, path: string): string => {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n };\n\n const flagHelpers = useMemo(() => ({ isEnabled, evaluations }), [evaluations, isEnabled]);\n\n // Register navigation items with the shell\n useEffect(() => {\n if (!registerNavItems) return;\n\n const navItems: NavItem[] = [\n {\n id: 'workspaces-all',\n label: 'All Workspaces',\n path: joinPath(basePath, 'list'),\n icon: <Building2 size={20} />,\n order: 10,\n group: 'workspaces',\n },\n // Projects — core feature, never gated\n {\n id: 'workspaces-projects',\n label: 'Projects',\n path: joinPath(basePath, 'projects'),\n icon: <FolderKanban size={20} />,\n order: 20,\n group: 'workspaces',\n },\n // Members — core feature, never gated\n {\n id: 'workspaces-members',\n label: 'Members',\n path: joinPath(basePath, 'members'),\n icon: <Users size={20} />,\n order: 30,\n group: 'workspaces',\n },\n // workspaces.activity — hidden until feature flag is enabled\n ...(flagHelpers.isEnabled('workspaces.activity')\n ? [\n {\n id: 'workspaces-activity',\n label: 'Activity',\n path: joinPath(basePath, 'activity'),\n icon: <Activity size={20} />,\n order: 40,\n group: 'workspaces',\n },\n ]\n : []),\n ];\n\n // Register and get cleanup function\n const cleanup = registerNavItems(navItems);\n\n // Return cleanup function to unregister on unmount\n return cleanup;\n }, [registerNavItems, basePath, flagHelpers]);\n\n // Register footer items (example: workspace status in left section)\n useEffect(() => {\n if (!registerFooterItems) return;\n\n const footerItems = [\n {\n id: 'workspaces-status',\n label: 'Workspaces Ready',\n type: 'status' as const,\n order: 10,\n section: 'left' as const,\n },\n ];\n\n const cleanup = registerFooterItems(footerItems);\n return cleanup;\n }, [registerFooterItems]);\n\n return (\n <FeatureFlagProvider workspaceId={null} gateway=\"global\" defaultEnabled={true}>\n <WorkspacesErrorBoundary>\n <WorkspacesProvider {...props}>\n <WorkspacesRoutes />\n </WorkspacesProvider>\n </WorkspacesErrorBoundary>\n </FeatureFlagProvider>\n );\n};\n"],"mappings":";;;;;;;;AAuCA,IAAa,KAA2C,MAAU;CAChE,IAAM,EAAE,qBAAkB,wBAAqB,cAAW,QAAQ,GAC5D,EAAE,cAAW,mBAAgB,GAAiB,EAG9C,KAAY,GAAc,OACZ,EAAK,SAAS,IAAI,GAAG,EAAK,MAAM,GAAG,GAAG,GAAG,MACzC,EAAK,WAAW,IAAI,GAAG,IAAO,IAAI,MAIhD,IAAc,SAAe;EAAE;EAAW;EAAa,GAAG,CAAC,GAAa,EAAU,CAAC;AAyEzF,QAtEA,QAAgB;AACT,QAgDL,QAHgB,EA3CY;GAC1B;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,OAAO;IAChC,MAAM,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IAC7B,OAAO;IACP,OAAO;IACR;GAED;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;IAChC,OAAO;IACP,OAAO;IACR;GAED;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,UAAU;IACnC,MAAM,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA;IACzB,OAAO;IACP,OAAO;IACR;GAED,GAAI,EAAY,UAAU,sBAAsB,GAC5C,CACE;IACE,IAAI;IACJ,OAAO;IACP,MAAM,EAAS,GAAU,WAAW;IACpC,MAAM,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;IAC5B,OAAO;IACP,OAAO;IACR,CACF,GACD,EAAE;GACP,CAGyC;IAIzC;EAAC;EAAkB;EAAU;EAAY,CAAC,EAG7C,QAAgB;AACT,QAaL,QADgB,EAVI,CAClB;GACE,IAAI;GACJ,OAAO;GACP,MAAM;GACN,OAAO;GACP,SAAS;GACV,CACF,CAE+C;IAE/C,CAAC,EAAoB,CAAC,EAGvB,kBAAC,GAAD;EAAqB,aAAa;EAAM,SAAQ;EAAS,gBAAgB;YACvE,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;GAAoB,GAAI;aACtB,kBAAC,GAAD,EAAoB,CAAA;GACD,CAAA,EACG,CAAA;EACN,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspacesRoutes.js","names":[],"sources":["../src/WorkspacesRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate } from 'react-router';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useWorkspacesContext } from './providers/WorkspacesProvider';\nimport { WorkspaceContextPathSync } from './components/WorkspaceContextPathSync';\n\n// Eager load - main entry point\nimport { WorkspacesListPage } from './pages/WorkspacesListPage';\n\n// Lazy load - secondary pages\nconst CreateWorkspacePage = lazy(() =>\n import('./pages/CreateWorkspacePage').then((m) => ({ default: m.CreateWorkspacePage }))\n);\nconst WorkspaceDetailPage = lazy(() =>\n import('./pages/WorkspaceDetailPage').then((m) => ({ default: m.WorkspaceDetailPage }))\n);\nconst WorkspaceSettingsPage = lazy(() =>\n import('./pages/WorkspaceSettingsPage').then((m) => ({ default: m.WorkspaceSettingsPage }))\n);\nconst ProjectsListPage = lazy(() =>\n import('./pages/ProjectsListPage').then((m) => ({ default: m.ProjectsListPage }))\n);\nconst ProjectDetailPage = lazy(() =>\n import('./pages/ProjectDetailPage').then((m) => ({ default: m.ProjectDetailPage }))\n);\nconst ProjectMembersPage = lazy(() =>\n import('./pages/ProjectMembersPage').then((m) => ({ default: m.ProjectMembersPage }))\n);\nconst MembersListPage = lazy(() =>\n import('./pages/MembersListPage').then((m) => ({ default: m.MembersListPage }))\n);\nconst ActivityListPage = lazy(() =>\n import('./pages/ActivityListPage').then((m) => ({ default: m.ActivityListPage }))\n);\nconst InviteAcceptancePage = lazy(() =>\n import('./pages/InviteAcceptancePage').then((m) => ({ default: m.InviteAcceptancePage }))\n);\nconst PendingInvitesPage = lazy(() =>\n import('./pages/PendingInvitesPage').then((m) => ({ default: m.PendingInvitesPage }))\n);\nconst WorkspaceInvitePage = lazy(() =>\n import('./pages/WorkspaceInvitePage').then((m) => ({ default: m.WorkspaceInvitePage }))\n);\n\n/**\n * Loading fallback for lazy-loaded components\n */\nfunction PageLoader() {\n return (\n <div className=\"flex items-center justify-center h-64\">\n <div className=\"animate-spin rounded-full size-8 border-b-2 border-primary\" />\n </div>\n );\n}\n\n/**\n * Fallback shown when a feature-gated route is disabled.\n */\nfunction FeatureDisabledFallback() {\n return (\n <div className=\"flex h-full items-center justify-center p-8 text-center\">\n <div>\n <h2 className=\"text-lg font-semibold mb-2 text-text-primary\">Feature Not Available</h2>\n <p className=\"text-sm text-text-secondary\">\n This feature is not currently available. Enable it under Feature Previews to access this\n functionality.\n </p>\n </div>\n </div>\n );\n}\n\n/**\n * Helper: wraps a page element in a FeatureGate.\n * defaultValue=false → hidden until explicitly enabled (unreleased feature).\n */\nfunction gated(flag: string, element: React.ReactNode, defaultValue = false): React.ReactElement {\n return (\n <FeatureGate flag={flag} defaultValue={defaultValue} fallback={<FeatureDisabledFallback />}>\n {element}\n </FeatureGate>\n );\n}\n\n/**\n * Catch-all redirect component that uses the basePath from context\n * to navigate to an absolute path, preventing infinite redirect loops.\n */\nfunction CatchAllRedirect() {\n const { basePath = '/workspaces' } = useWorkspacesContext();\n return <Navigate to={`${basePath}/list`} replace />;\n}\n\nfunction CurrentWorkspaceInviteRedirect() {\n const { basePath = '/workspaces', workspaceId } = useWorkspacesContext();\n\n if (!workspaceId) {\n return <WorkspaceInvitePage />;\n }\n\n return <Navigate to={`${basePath}/${workspaceId}/invite`} replace />;\n}\n\n/**\n * Internal routing for the Workspaces microfrontend.\n * Defines all routes and their corresponding page components.\n *\n * - WorkspacesListPage: Eager loaded (main entry point)\n * - All other pages: Lazy loaded for better performance\n * - Projects, Members, Activity: gated by feature flags (unreleased)\n */\nexport function WorkspacesRoutes() {\n return (\n <Suspense fallback={<PageLoader />}>\n {/* Keeps the scoped `:workspaceId` URL segment in step with the active context\n when the user switches workspace via the chrome context switcher. */}\n <WorkspaceContextPathSync />\n <Routes>\n {/* Default redirect */}\n <Route index element={<Navigate to=\"list\" replace />} />\n\n {/* Workspaces List - Eager loaded */}\n <Route path=\"list\" element={<WorkspacesListPage />} />\n\n {/* Create Workspace (must come before :workspaceId to avoid conflicts) */}\n <Route path=\"new\" element={<CreateWorkspacePage />} />\n\n {/* Pending Invites (must come before :workspaceId to avoid conflicts) */}\n <Route path=\"invites\" element={<PendingInvitesPage />} />\n\n {/* Workspace Invite */}\n <Route path=\"invite\" element={<CurrentWorkspaceInviteRedirect />} />\n\n {/* Invitation Acceptance (must come before :workspaceId to avoid conflicts) */}\n <Route path=\"invite/:invitationId\" element={<InviteAcceptancePage />} />\n\n {/* Workspace Detail */}\n <Route path=\":workspaceId\" element={<WorkspaceDetailPage />} />\n <Route\n path=\":workspaceId/settings\"\n element={gated('workspaces.settings', <WorkspaceSettingsPage />, true)}\n />\n <Route path=\":workspaceId/invite\" element={<WorkspaceInvitePage />} />\n\n {/* Projects — core feature, never gated */}\n <Route path=\"projects\" element={<ProjectsListPage />} />\n <Route path=\":workspaceId/projects\" element={<ProjectsListPage />} />\n <Route path=\":workspaceId/projects/:projectId\" element={<ProjectDetailPage />} />\n <Route path=\":workspaceId/projects/:projectId/members\" element={<ProjectMembersPage />} />\n\n {/* Members — core feature, never gated */}\n <Route path=\"members\" element={<MembersListPage />} />\n <Route path=\":workspaceId/members\" element={<MembersListPage />} />\n\n {/* Activity — gated by workspaces.activity */}\n <Route path=\"activity\" element={gated('workspaces.activity', <ActivityListPage />)} />\n <Route\n path=\":workspaceId/activity\"\n element={gated('workspaces.activity', <ActivityListPage />)}\n />\n\n {/* Catch-all redirect - uses absolute path to prevent infinite loops */}\n <Route path=\"*\" element={<CatchAllRedirect />} />\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"CreateProjectDialog.js","names":[],"sources":["../../src/components/CreateProjectDialog.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n Textarea,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateProject } from '../hooks/useProjectMutations';\nimport { useTr } from '../hooks/useTr';\n\nfunction createProjectSchema(tr: ReturnType<typeof useTr>) {\n return z.object({\n name: z\n .string()\n .min(2, tr('createProjectDialog.validation.nameMin', 'Name must be at least 2 characters'))\n .max(\n 100,\n tr('createProjectDialog.validation.nameMax', 'Name must be less than 100 characters')\n ),\n key: z\n .string()\n .min(2, tr('createProjectDialog.validation.keyMin', 'Key must be at least 2 characters'))\n .max(10, tr('createProjectDialog.validation.keyMax', 'Key must be less than 10 characters'))\n .regex(\n /^[A-Z0-9_-]+$/,\n tr(\n 'createProjectDialog.validation.keyFormat',\n 'Key must be uppercase letters, numbers, underscores, or hyphens'\n )\n ),\n description: z.string().optional(),\n });\n}\n\ntype CreateProjectFormData = z.infer<ReturnType<typeof createProjectSchema>>;\n\ninterface CreateProjectDialogProps {\n workspaceId?: string;\n}\n\nexport function CreateProjectDialog({ workspaceId }: CreateProjectDialogProps) {\n const tr = useTr();\n const { isCreateProjectDialogOpen, closeCreateProjectDialog } = useWorkspacesStore();\n const { mutate: createProject, isPending } = useCreateProject(workspaceId);\n\n const schema = useMemo(() => createProjectSchema(tr), [tr]);\n\n const form = useForm({\n schema,\n defaultValues: {\n name: '',\n key: '',\n description: '',\n },\n });\n\n const onSubmit = (data: CreateProjectFormData) => {\n const input = {\n name: data.name,\n key: data.key,\n config: data.description ? { description: data.description } : undefined,\n };\n\n createProject(input, {\n onSuccess: () => {\n closeCreateProjectDialog();\n form.reset();\n },\n onError: (error) => {\n console.error('Failed to create project:', error);\n },\n });\n };\n\n const handleOpenChange = (open: boolean) => {\n if (!open) {\n closeCreateProjectDialog();\n form.reset();\n }\n };\n\n // Auto-generate key from name\n const handleNameChange = (name: string) => {\n const autoKey = name\n .toUpperCase()\n .replace(/[^A-Z0-9\\s]/g, '')\n .replace(/\\s+/g, '_')\n .slice(0, 10);\n\n if (!form.getValues('key')) {\n form.setValue('key', autoKey);\n }\n };\n\n return (\n <ResponsiveDialog open={isCreateProjectDialogOpen} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('createProjectDialog.title', 'Create Project')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'createProjectDialog.description',\n 'Create a new project in your workspace. Projects help organize your work.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('createProjectDialog.nameLabel', 'Project Name')}</FormLabel>\n <FormControl>\n <Input\n {...field}\n placeholder={tr('createProjectDialog.namePlaceholder', 'My Project')}\n onChange={(e) => {\n field.onChange(e);\n handleNameChange(e.target.value);\n }}\n />\n </FormControl>\n <FormDescription>\n {tr('createProjectDialog.nameHint', 'A descriptive name for your project')}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"key\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('createProjectDialog.keyLabel', 'Project Key')}</FormLabel>\n <FormControl>\n <Input\n {...field}\n placeholder={tr('createProjectDialog.keyPlaceholder', 'MYPROJ')}\n />\n </FormControl>\n <FormDescription>\n {tr(\n 'createProjectDialog.keyHint',\n 'Short identifier (uppercase letters, numbers, underscores)'\n )}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"description\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tr('createProjectDialog.descriptionLabel', 'Description (Optional)')}\n </FormLabel>\n <FormControl>\n <Textarea\n {...field}\n placeholder={tr(\n 'createProjectDialog.descriptionPlaceholder',\n 'Brief description of the project'\n )}\n rows={3}\n />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <div className=\"flex justify-end gap-3 pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={isPending}\n >\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isPending}>\n {isPending\n ? tr('createProjectDialog.creating', 'Creating...')\n : tr('createProjectDialog.submit', 'Create Project')}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"CreateWorkspaceDialog.js","names":[],"sources":["../../src/components/CreateWorkspaceDialog.tsx"],"sourcesContent":["import { useEffect, useMemo } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n ResponsiveDialogFooter,\n Button,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useMyTenants, useMyOrganizations } from '../hooks/useTenants';\nimport { generateSlug } from '../utils/slug';\nimport { useTr } from '../hooks/useTr';\n\ntype CreateWorkspaceFormData = z.infer<ReturnType<typeof createWorkspaceSchema>>;\n\nfunction createWorkspaceSchema(tr: ReturnType<typeof useTr>) {\n return z.object({\n name: z\n .string()\n .min(2, tr('createWorkspaceDialog.validation.nameMin', 'Name must be at least 2 characters'))\n .max(100),\n slug: z\n .string()\n .min(2, tr('createWorkspaceDialog.validation.slugMin', 'Slug must be at least 2 characters'))\n .max(50)\n .regex(\n /^[a-z0-9-]+$/,\n tr(\n 'createWorkspaceDialog.validation.slugFormat',\n 'Slug can only contain lowercase letters, numbers, and hyphens'\n )\n ),\n tenantId: z\n .string()\n .min(1, tr('createWorkspaceDialog.validation.tenantRequired', 'Please select a tenant')),\n organizationId: z\n .string()\n .min(\n 1,\n tr('createWorkspaceDialog.validation.organizationRequired', 'Please select an organization')\n ),\n });\n}\n\nexport function CreateWorkspaceDialog() {\n const tr = useTr();\n const { isCreateWorkspaceDialogOpen, closeCreateWorkspaceDialog } = useWorkspacesStore();\n const { mutate: createWorkspace, isPending } = useCreateWorkspace();\n const { data: tenantsData, isLoading: isLoadingTenants, error: tenantsError } = useMyTenants();\n const {\n data: organizationsData,\n isLoading: isLoadingOrganizations,\n error: organizationsError,\n } = useMyOrganizations();\n\n const schema = useMemo(() => createWorkspaceSchema(tr), [tr]);\n\n const tenants = useMemo(() => tenantsData?.items ?? [], [tenantsData]);\n const allOrganizations = useMemo(() => organizationsData?.items ?? [], [organizationsData]);\n\n // Prefer the shared tenant (or the only one) — with a single shared tenant\n // there is nothing to choose, so we auto-select it and hide the picker.\n const defaultTenantId = useMemo(() => {\n const sharedTenant = tenants.find(\n (tenant) => tenant.type === 'SHARED' || tenant.slug === 'burdenoff-shared'\n );\n return sharedTenant?.id ?? tenants[0]?.id ?? '';\n }, [tenants]);\n\n const form = useForm<CreateWorkspaceFormData>({\n schema,\n defaultValues: {\n name: '',\n slug: '',\n tenantId: '',\n organizationId: '',\n },\n });\n\n const selectedTenantId = form.watch('tenantId');\n const workspaceName = form.watch('name');\n\n // Auto-select the default tenant when the user has no real choice.\n useEffect(() => {\n if (!form.getValues('tenantId') && defaultTenantId) {\n form.setValue('tenantId', defaultTenantId, { shouldValidate: true });\n }\n }, [defaultTenantId, form]);\n\n // Filter organizations by selected tenant\n const filteredOrganizations = useMemo(() => {\n if (!selectedTenantId) return [];\n return allOrganizations.filter((org) => org.tenantId === selectedTenantId);\n }, [selectedTenantId, allOrganizations]);\n\n // Auto-generate slug from name (clear when name is empty)\n useEffect(() => {\n form.setValue('slug', workspaceName ? generateSlug(workspaceName) : '');\n }, [workspaceName, form]);\n\n // Reset organizationId when tenant changes\n useEffect(() => {\n form.setValue('organizationId', '');\n }, [selectedTenantId, form]);\n\n const onSubmit = (data: CreateWorkspaceFormData) => {\n createWorkspace(\n {\n name: data.name,\n slug: data.slug,\n tenantId: data.tenantId,\n organizationId: data.organizationId,\n },\n {\n onSuccess: () => {\n closeCreateWorkspaceDialog();\n form.reset();\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={isCreateWorkspaceDialogOpen} onOpenChange={closeCreateWorkspaceDialog}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('createWorkspaceDialog.title', 'Create Workspace')}\n </ResponsiveDialogTitle>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n {/* Workspace Name */}\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('createWorkspaceDialog.nameLabel', 'Workspace Name *')}</FormLabel>\n <FormControl>\n <Input\n placeholder={tr('createWorkspaceDialog.namePlaceholder', 'My Workspace')}\n {...field}\n />\n </FormControl>\n <FormDescription>\n {tr('createWorkspaceDialog.nameHint', 'A descriptive name for your workspace')}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Slug */}\n <FormField\n control={form.control}\n name=\"slug\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('createWorkspaceDialog.slugLabel', 'Slug *')}</FormLabel>\n <FormControl>\n <Input\n placeholder={tr('createWorkspaceDialog.slugPlaceholder', 'my-workspace')}\n {...field}\n />\n </FormControl>\n <FormDescription>\n {tr(\n 'createWorkspaceDialog.slugHint',\n 'URL-friendly identifier (auto-generated)'\n )}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {/* Tenant Selection — shown only when the user has more than one\n tenant to choose between. With a single shared tenant it is\n auto-selected and hidden (loading/error/empty still surface). */}\n {tenants.length !== 1 ? (\n <FormField\n control={form.control}\n name=\"tenantId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('createWorkspaceDialog.tenantLabel', 'Tenant *')}</FormLabel>\n {isLoadingTenants ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : tenantsError ? (\n <div className=\"flex items-center gap-2 text-status-error-text text-sm\">\n <AlertCircle size={16} />\n {tr('createWorkspaceDialog.failedToLoadTenants', 'Failed to load tenants')}\n </div>\n ) : tenants.length === 0 ? (\n <div className=\"text-text-secondary text-sm\">\n {tr(\n 'createWorkspaceDialog.noTenantsAvailable',\n 'No tenants available. Contact your administrator.'\n )}\n </div>\n ) : (\n <Select onValueChange={field.onChange} value={field.value}>\n <FormControl>\n <SelectTrigger>\n <SelectValue\n placeholder={tr(\n 'createWorkspaceDialog.selectTenantPlaceholder',\n 'Select a tenant'\n )}\n />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {tenants.map((tenant) => (\n <SelectItem key={tenant.id} value={tenant.id}>\n {tenant.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>\n {tr(\n 'createWorkspaceDialog.tenantHint',\n 'Select which tenant this workspace belongs to'\n )}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n ) : null}\n\n {/* Organization Selection */}\n <FormField\n control={form.control}\n name=\"organizationId\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tr('createWorkspaceDialog.organizationLabel', 'Organization *')}\n </FormLabel>\n {isLoadingOrganizations ? (\n <Skeleton className=\"h-10 w-full\" />\n ) : organizationsError ? (\n <div className=\"flex items-center gap-2 text-status-error-text text-sm\">\n <AlertCircle size={16} />\n {tr(\n 'createWorkspaceDialog.failedToLoadOrganizations',\n 'Failed to load organizations'\n )}\n </div>\n ) : !selectedTenantId ? (\n <div className=\"text-text-secondary text-sm\">\n {tr(\n 'createWorkspaceDialog.selectTenantFirst',\n 'Please select a tenant first'\n )}\n </div>\n ) : filteredOrganizations.length === 0 ? (\n <div className=\"text-text-secondary text-sm\">\n {tr(\n 'createWorkspaceDialog.noOrganizationsFound',\n 'No organizations found for this tenant'\n )}\n </div>\n ) : (\n <Select\n onValueChange={field.onChange}\n value={field.value}\n disabled={!selectedTenantId}\n >\n <FormControl>\n <SelectTrigger>\n <SelectValue\n placeholder={tr(\n 'createWorkspaceDialog.selectOrganizationPlaceholder',\n 'Select an organization'\n )}\n />\n </SelectTrigger>\n </FormControl>\n <SelectContent>\n {filteredOrganizations.map((org) => (\n <SelectItem key={org.id} value={org.id}>\n {org.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n <FormDescription>\n {tr(\n 'createWorkspaceDialog.organizationHint',\n 'Select which organization owns this workspace'\n )}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <ResponsiveDialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={closeCreateWorkspaceDialog}\n disabled={isPending}\n >\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button\n type=\"submit\"\n disabled={isPending || isLoadingTenants || isLoadingOrganizations}\n >\n {isPending\n ? tr('createWorkspaceDialog.creating', 'Creating...')\n : tr('createWorkspaceDialog.submit', 'Create Workspace')}\n </Button>\n </ResponsiveDialogFooter>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"InviteMemberDialog.js","names":[],"sources":["../../src/components/InviteMemberDialog.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { AlertCircle } from 'lucide-react';\nimport { useTr } from '../hooks/useTr';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n Badge,\n Checkbox,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useCreateInvitation } from '../hooks/useMemberMutations';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceRoles } from '../hooks/useWorkspaceRoles';\n\nfunction inviteMemberSchema(tr: ReturnType<typeof useTr>) {\n return z.object({\n email: z\n .string()\n .email(tr('inviteMemberDialog.validation.emailInvalid', 'Invalid email address')),\n // Using a lambda default instead of `.default([])` to keep input type required\n // (zod v4 widens defaulted inputs to `T | undefined`, which breaks useForm typing).\n roleIds: z.array(z.string()),\n projectIds: z.array(z.string()),\n });\n}\n\ntype InviteMemberFormData = z.infer<ReturnType<typeof inviteMemberSchema>>;\n\ninterface InviteMemberDialogProps {\n /** Workspace ID to invite the member to (from URL params) */\n workspaceId?: string;\n}\n\nexport function InviteMemberDialog({ workspaceId }: InviteMemberDialogProps) {\n const tr = useTr();\n const { isInviteMemberDialogOpen, closeInviteMemberDialog } = useWorkspacesStore();\n const { mutate: createInvitation, isPending } = useCreateInvitation(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const [error, setError] = useState<string | null>(null);\n const availableProjects = projectList?.items ?? [];\n\n const schema = useMemo(() => inviteMemberSchema(tr), [tr]);\n\n const form = useForm<InviteMemberFormData>({\n schema,\n defaultValues: {\n email: '',\n roleIds: [],\n projectIds: [],\n },\n });\n\n const onSubmit = (data: InviteMemberFormData) => {\n setError(null);\n\n if (!workspaceId) {\n setError(\n tr(\n 'inviteMemberDialog.noWorkspaceError',\n 'No workspace selected. Please select a workspace first.'\n )\n );\n return;\n }\n\n createInvitation(\n {\n email: data.email,\n roleIds: data.roleIds,\n workspaceId,\n ...(data.projectIds.length > 0\n ? {\n context: {\n type: 'workspace.invitation',\n metadata: { projectIds: data.projectIds },\n },\n }\n : {}),\n },\n {\n onSuccess: () => {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n },\n onError: (err) => {\n const errorMessage =\n err instanceof Error\n ? err.message\n : tr('inviteMemberDialog.sendError', 'Failed to send invitation');\n console.error('Failed to create invitation:', err);\n setError(errorMessage);\n },\n }\n );\n };\n\n const handleOpenChange = (open: boolean) => {\n if (!open) {\n closeInviteMemberDialog();\n form.reset();\n setError(null);\n }\n };\n\n return (\n <ResponsiveDialog open={isInviteMemberDialogOpen} onOpenChange={handleOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(88vh,900px)] flex-col overflow-hidden sm:max-w-[960px] lg:max-w-[1120px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('inviteMemberDialog.title', 'Invite Member')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'inviteMemberDialog.description',\n \"Invite a new member to this workspace by email. They'll receive an invitation link to join.\"\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"min-h-0 flex-1 overflow-y-auto pr-2\">\n <div className=\"space-y-5 pb-4 pr-2\">\n {error && (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n\n <FormField\n control={form.control}\n name=\"email\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tr('inviteMemberDialog.emailLabel', 'Email Address *')}\n </FormLabel>\n <FormControl>\n <Input\n type=\"email\"\n placeholder={tr(\n 'inviteMemberDialog.emailPlaceholder',\n 'member@example.com'\n )}\n {...field}\n disabled={isPending}\n onChange={(e) => {\n field.onChange(e);\n setError(null);\n }}\n />\n </FormControl>\n <FormDescription>\n {tr(\n 'inviteMemberDialog.emailHint',\n 'Enter the email address of the person you want to invite'\n )}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"roleIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>\n {tr('inviteMemberDialog.rolesLabel', 'Roles (Optional)')}\n </FormLabel>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'inviteMemberDialog.rolesHint',\n 'Choose roles. Hover any role to preview its full description.'\n )}\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">\n {field.value.length}{' '}\n {tr('inviteMemberDialog.selectedCount', 'selected')}\n </Badge>\n ) : null}\n </div>\n <FormControl>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('inviteMemberDialog.loadingRoles', 'Loading roles…')}\n </p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'inviteMemberDialog.noRoles',\n 'No assignable roles were returned for this workspace.'\n )}\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = field.value.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n }`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, role.id]\n : field.value.filter((id) => id !== role.id);\n field.onChange(nextValue);\n }}\n aria-label={tr(\n 'inviteMemberDialog.selectRoleAriaLabel',\n 'Select {{roleName}}',\n { roleName: role.name }\n )}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-text-secondary\">\n {role.description ||\n tr(\n 'inviteMemberDialog.noRoleDescription',\n 'No description available for this role.'\n )}\n </p>\n {role.isSystem ? (\n <p className=\"mt-2 text-xs\">\n {tr('inviteMemberDialog.systemRole', 'System role')}\n </p>\n ) : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"projectIds\"\n render={({ field }) => (\n <FormItem>\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <FormLabel>\n {tr(\n 'inviteMemberDialog.projectsLabel',\n 'Projects to add on acceptance (Optional)'\n )}\n </FormLabel>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'inviteMemberDialog.projectsHint',\n 'The invited user will be added to these projects automatically after they accept the workspace invitation.'\n )}\n </p>\n </div>\n {field.value.length > 0 ? (\n <Badge variant=\"secondary\">\n {field.value.length}{' '}\n {tr('inviteMemberDialog.selectedCount', 'selected')}\n </Badge>\n ) : null}\n </div>\n <FormControl>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('inviteMemberDialog.loadingProjects', 'Loading projects…')}\n </p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'inviteMemberDialog.noProjects',\n 'No active projects are available in this workspace yet.'\n )}\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = field.value.includes(project.id);\n\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n }`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">\n {project.name}\n </p>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={isPending}\n onCheckedChange={(checked) => {\n const nextValue = checked\n ? [...field.value, project.id]\n : field.value.filter((id) => id !== project.id);\n field.onChange(nextValue);\n }}\n aria-label={tr(\n 'inviteMemberDialog.selectProjectAriaLabel',\n 'Select {{projectName}}',\n { projectName: project.name }\n )}\n />\n </label>\n );\n })\n )}\n </div>\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </div>\n </div>\n\n <div className=\"flex justify-end gap-3 border-t pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => handleOpenChange(false)}\n disabled={isPending}\n >\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isPending}>\n {isPending\n ? tr('inviteMemberDialog.sending', 'Sending...')\n : tr('inviteMemberDialog.submit', 'Send Invitation')}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;;;;;AAkCA,SAAS,EAAmB,GAA8B;AACxD,QAAO,EAAE,OAAO;EACd,OAAO,EACJ,QAAQ,CACR,MAAM,EAAG,8CAA8C,wBAAwB,CAAC;EAGnF,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;EAC5B,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC;EAChC,CAAC;;AAUJ,SAAgB,EAAmB,EAAE,kBAAwC;CAC3E,IAAM,IAAK,GAAO,EACZ,EAAE,6BAA0B,+BAA4B,GAAoB,EAC5E,EAAE,QAAQ,GAAkB,iBAAc,EAAoB,EAAY,EAC1E,EAAE,MAAM,IAAiB,EAAE,EAAE,WAAW,MAAmB,EAAkB,EAAY,EACzF,EAAE,MAAM,GAAa,WAAW,MAAsB,EAC1D,EAAE,UAAU,IAAO,EACnB,EAAE,OAAO,KAAK,EACd,EACD,EACK,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,IAAoB,GAAa,SAAS,EAAE,EAI5C,IAAO,EAA8B;EACzC,QAHa,QAAc,EAAmB,EAAG,EAAE,CAAC,EAAG,CAAC;EAIxD,eAAe;GACb,OAAO;GACP,SAAS,EAAE;GACX,YAAY,EAAE;GACf;EACF,CAAC,EAEI,KAAY,MAA+B;AAG/C,MAFA,EAAS,KAAK,EAEV,CAAC,GAAa;AAChB,KACE,EACE,uCACA,0DACD,CACF;AACD;;AAGF,IACE;GACE,OAAO,EAAK;GACZ,SAAS,EAAK;GACd;GACA,GAAI,EAAK,WAAW,SAAS,IACzB,EACE,SAAS;IACP,MAAM;IACN,UAAU,EAAE,YAAY,EAAK,YAAY;IAC1C,EACF,GACD,EAAE;GACP,EACD;GACE,iBAAiB;AAGf,IAFA,GAAyB,EACzB,EAAK,OAAO,EACZ,EAAS,KAAK;;GAEhB,UAAU,MAAQ;IAChB,IAAM,IACJ,aAAe,QACX,EAAI,UACJ,EAAG,gCAAgC,4BAA4B;AAErE,IADA,QAAQ,MAAM,gCAAgC,EAAI,EAClD,EAAS,EAAa;;GAEzB,CACF;IAGG,KAAoB,MAAkB;AAC1C,EAAK,MACH,GAAyB,EACzB,EAAK,OAAO,EACZ,EAAS,KAAK;;AAIlB,QACE,kBAAC,GAAD;EAAkB,MAAM;EAA0B,cAAc;YAC9D,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,EAAG,4BAA4B,gBAAgB,EAC1B,CAAA,EACxB,kBAAC,GAAD,EAAA,UACG,EACC,kCACA,8FACD,EAC2B,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,GAAD;IAAc,GAAI;cAChB,kBAAC,QAAD;KAAM,UAAU,EAAK,aAAa,EAAS;KAAE,WAAU;eAAvD,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACG,KACC,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,wBAAyB,CAAA,EAChD,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CAChB;;QAGR,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,GAAD,EAAA,UACG,EAAG,iCAAiC,kBAAkB,EAC7C,CAAA;UACZ,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;WACE,MAAK;WACL,aAAa,EACX,uCACA,qBACD;WACD,GAAI;WACJ,UAAU;WACV,WAAW,MAAM;AAEf,YADA,EAAM,SAAS,EAAE,EACjB,EAAS,KAAK;;WAEhB,CAAA,EACU,CAAA;UACd,kBAAC,GAAD,EAAA,UACG,EACC,gCACA,2DACD,EACe,CAAA;UAClB,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QAEF,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EAAG,iCAAiC,mBAAmB,EAC9C,CAAA,EACZ,kBAAC,KAAD;aAAG,WAAU;uBACV,EACC,gCACA,gEACD;aACC,CAAA,CACA;eACL,EAAM,MAAM,SAAS,IACpB,kBAAC,GAAD;YAAO,SAAQ;sBAAf;aACG,EAAM,MAAM;aAAQ;aACpB,EAAG,oCAAoC,WAAW;aAC7C;gBACN,KACA;;UACN,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,IACC,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAG,mCAAmC,iBAAiB;YACtD,CAAA,GACF,EAAe,WAAW,IAC5B,kBAAC,KAAD;YAAG,WAAU;sBACV,EACC,8BACA,wDACD;YACC,CAAA,GAEJ,EAAe,KAAK,MAAS;YAC3B,IAAM,IAAa,EAAM,MAAM,SAAS,EAAK,GAAG;AAChD,mBACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD;aAAgB,SAAA;uBACd,kBAAC,SAAD;cACE,WAAW,iOACT,IACI,uEACA;wBAJR,CAOE,kBAAC,QAAD;eAAM,WAAU;yBAA2B,EAAK;eAAY,CAAA,EAC5D,kBAAC,GAAD;eACE,SAAS;eACT,UAAU;eACV,kBAAkB,MAAY;gBAC5B,IAAM,IAAY,IACd,CAAC,GAAG,EAAM,OAAO,EAAK,GAAG,GACzB,EAAM,MAAM,QAAQ,MAAO,MAAO,EAAK,GAAG;AAC9C,kBAAM,SAAS,EAAU;;eAE3B,cAAY,EACV,0CACA,uBACA,EAAE,UAAU,EAAK,MAAM,CACxB;eACD,CAAA,CACI;;aACO,CAAA,EACjB,kBAAC,GAAD;aAAgB,WAAU;uBAA1B;cACE,kBAAC,KAAD;eAAG,WAAU;yBAAe,EAAK;eAAS,CAAA;cAC1C,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAK,eACJ,EACE,wCACA,0CACD;eACD,CAAA;cACH,EAAK,WACJ,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAG,iCAAiC,cAAc;eACjD,CAAA,GACF;cACW;eACT,EAAA,EA1CI,EAAK,GA0CT;aAEZ;WAEA,CAAA,EACU,CAAA,EACN,CAAA;UACd,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QAEF,kBAAC,GAAD;SACE,SAAS,EAAK;SACd,MAAK;SACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACE,kBAAC,GAAD,EAAA,UACG,EACC,oCACA,2CACD,EACS,CAAA,EACZ,kBAAC,KAAD;aAAG,WAAU;uBACV,EACC,mCACA,6GACD;aACC,CAAA,CACA;eACL,EAAM,MAAM,SAAS,IACpB,kBAAC,GAAD;YAAO,SAAQ;sBAAf;aACG,EAAM,MAAM;aAAQ;aACpB,EAAG,oCAAoC,WAAW;aAC7C;gBACN,KACA;;UACN,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;WAAK,WAAU;qBACZ,IACC,kBAAC,KAAD;YAAG,WAAU;sBACV,EAAG,sCAAsC,oBAAoB;YAC5D,CAAA,GACF,EAAkB,WAAW,IAC/B,kBAAC,KAAD;YAAG,WAAU;sBACV,EACC,iCACA,0DACD;YACC,CAAA,GAEJ,EAAkB,KAAK,MAAY;YACjC,IAAM,IAAa,EAAM,MAAM,SAAS,EAAQ,GAAG;AAEnD,mBACE,kBAAC,SAAD;aAEE,WAAW,kMACT,IACI,uEACA;uBALR,CAQE,kBAAC,OAAD;cAAK,WAAU;wBAAf,CACE,kBAAC,KAAD;eAAG,WAAU;yBACV,EAAQ;eACP,CAAA,EACJ,kBAAC,OAAD;eAAK,WAAU;yBAAf,CACE,kBAAC,QAAD;gBAAM,WAAU;0BAAsB,EAAQ;gBAAW,CAAA,EACzD,kBAAC,GAAD;gBAAO,SAAQ;0BAAW,EAAQ;gBAAe,CAAA,CAC7C;iBACF;iBACN,kBAAC,GAAD;cACE,SAAS;cACT,UAAU;cACV,kBAAkB,MAAY;eAC5B,IAAM,IAAY,IACd,CAAC,GAAG,EAAM,OAAO,EAAQ,GAAG,GAC5B,EAAM,MAAM,QAAQ,MAAO,MAAO,EAAQ,GAAG;AACjD,iBAAM,SAAS,EAAU;;cAE3B,cAAY,EACV,6CACA,0BACA,EAAE,aAAa,EAAQ,MAAM,CAC9B;cACD,CAAA,CACI;eA/BD,EAAQ,GA+BP;aAEV;WAEA,CAAA,EACM,CAAA;UACd,kBAAC,GAAD,EAAe,CAAA;UACN,EAAA,CAAA;SAEb,CAAA;QACE;;MACF,CAAA,EAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OACE,MAAK;OACL,SAAQ;OACR,eAAe,EAAiB,GAAM;OACtC,UAAU;iBAET,EAAG,iBAAiB,SAAS;OACvB,CAAA,EACT,kBAAC,GAAD;OAAQ,MAAK;OAAS,UAAU;iBAC7B,IACG,EAAG,8BAA8B,aAAa,GAC9C,EAAG,6BAA6B,kBAAkB;OAC/C,CAAA,CACL;QACD;;IACM,CAAA,CACS;;EACT,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MemberListItem.js","names":[],"sources":["../../src/components/MemberListItem.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n GlassCard,\n CardContent,\n Button,\n Badge,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuTrigger,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { MoreHorizontal, UserMinus } from 'lucide-react';\nimport { ActorIdentity, useActorProfiles, type ActorInput } from '@burdenoff/fe-libs/shared';\nimport { useRemoveWorkspaceMember } from '../hooks/useMemberMutations';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useTr } from '../hooks/useTr';\nimport type { WorkspaceMember } from '../types';\n\nexport interface MemberListItemProps {\n member: WorkspaceMember;\n onUpdateRoles?: (memberId: string, roleIds: string[]) => void;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n canRemove?: boolean;\n}\n\nexport function MemberListItem({\n member,\n selected = false,\n onToggleSelect,\n canRemove = false,\n}: MemberListItemProps) {\n const tr = useTr();\n const [showRemoveDialog, setShowRemoveDialog] = useState(false);\n const { mutate: removeMember, isPending: isRemoving } = useRemoveWorkspaceMember(\n member.workspaceId\n );\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n\n // Resolve userId → displayName + email via the shared actor profile cache.\n // UUIDs must never headline a row — the resolved identity goes in primary\n // position; the raw ID lives in the drill-down inside ActorIdentity.\n const actors = useMemo<ActorInput[]>(\n () => [{ actorId: member.userId, actorType: 'user' }],\n [member.userId]\n );\n const profileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId: member.workspaceId,\n });\n const profile = profileMap[member.userId];\n\n const handleRemove = () => {\n void nativeImpact('medium');\n removeMember(member.id, {\n onSuccess: () => {\n void nativeNotify('success');\n setShowRemoveDialog(false);\n },\n onError: () => {\n void nativeNotify('error');\n },\n });\n };\n\n return (\n <>\n <GlassCard\n treatment=\"plain\"\n className={`border-border-seam ${\n selected ? 'ring-2 ring-action-primary-bg/25 border-action-primary-bg/50' : ''\n }`}\n >\n <CardContent className=\"p-4\">\n <div className=\"flex items-center justify-between gap-4\">\n <div className=\"flex items-center gap-3 flex-1 min-w-0\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n className=\"size-4 rounded border-border-default text-action-primary-bg\"\n aria-label={tr('memberListItem.selectMemberAriaLabel', 'Select member {{name}}', {\n name: profile?.displayName ?? '',\n })}\n />\n )}\n\n <div className=\"flex-1 min-w-0\">\n {profile ? <ActorIdentity profile={profile} variant=\"cell\" /> : null}\n <div className=\"mt-1 flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-xs\">\n {tr('memberListItem.workspaceMember', 'Workspace member')}\n </Badge>\n <span className=\"text-xs text-text-secondary\">\n {tr('memberListItem.joined', 'Joined {{time}}', {\n time: formatRelativeTime(member.createdAt),\n })}\n </span>\n </div>\n </div>\n </div>\n\n {canRemove && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button variant=\"ghost\" size=\"icon\" className=\"size-8 flex-shrink-0\">\n <MoreHorizontal className=\"size-4\" />\n <span className=\"sr-only\">\n {tr('memberListItem.openActions', 'Open member actions')}\n </span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <button\n type=\"button\"\n onClick={() => setShowRemoveDialog(true)}\n className=\"flex w-full items-center rounded-sm px-2 py-1.5 text-sm text-status-danger-text outline-none transition-colors hover:bg-action-ghost-bg-hover focus:bg-action-ghost-bg-hover\"\n >\n <UserMinus className=\"mr-2 size-4\" />\n {tr('memberListItem.removeMember', 'Remove Member')}\n </button>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </CardContent>\n </GlassCard>\n\n <AlertDialog open={showRemoveDialog} onOpenChange={setShowRemoveDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>{tr('memberListItem.removeTitle', 'Remove Member')}</AlertDialogTitle>\n <AlertDialogDescription>\n {tr(\n 'memberListItem.removeDescription',\n 'Are you sure you want to remove {{name}} from the workspace? This action cannot be undone.',\n {\n name:\n profile?.displayName ?? tr('memberListItem.thisMemberFallback', 'this member'),\n }\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isRemoving}>\n {tr('common.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={handleRemove}\n disabled={isRemoving}\n className=\"bg-status-danger-bg text-status-danger-text hover:bg-status-danger-bg/90\"\n >\n {isRemoving\n ? tr('memberListItem.removing', 'Removing...')\n : tr('memberListItem.removeButton', 'Remove Member')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProjectCard.js","names":[],"sources":["../../src/components/ProjectCard.tsx"],"sourcesContent":["import type React from 'react';\nimport { useState } from 'react';\nimport { FolderKanban, Archive, MoreVertical, Fingerprint, Pencil } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport {\n GlassCard,\n CardContent,\n CardHeader,\n CardTitle,\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n EntityIdModal,\n useEntityIdModal,\n} from '@burdenoff/fe-libs/ui';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\n\nimport type { Project, ProjectTag } from '../types';\nimport { formatDate } from '../utils/formatters';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n} from '../hooks/useProjectMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { RenameProjectDialog } from './RenameProjectDialog';\nimport { useTr } from '../hooks/useTr';\n\ninterface ProjectCardProps {\n project: Project;\n selected?: boolean;\n onToggleSelect?: (checked: boolean) => void;\n onTagClick?: (tag: ProjectTag) => void;\n canArchive?: boolean;\n canDelete?: boolean;\n canRename?: boolean;\n}\n\nexport function ProjectCard({\n project,\n selected = false,\n onToggleSelect,\n onTagClick,\n canArchive = false,\n canDelete = false,\n canRename = false,\n}: ProjectCardProps) {\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { mutate: archiveProject } = useArchiveProject(project.workspaceId);\n const { mutate: unarchiveProject } = useUnarchiveProject(project.workspaceId);\n const { mutate: deleteProject } = useDeleteProject(project.workspaceId);\n const idModal = useEntityIdModal();\n const [showRenameDialog, setShowRenameDialog] = useState(false);\n const projectTags = getProjectDisplayTags(project);\n const tr = useTr();\n\n const handleCardClick = () => {\n navigate(withCurrentSearch(`${basePath}/${project.workspaceId}/projects/${project.id}`));\n };\n\n const handleArchive = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (project.archived) {\n unarchiveProject(project.id);\n } else {\n archiveProject(project.id);\n }\n };\n\n const handleDelete = async (e: React.MouseEvent) => {\n e.stopPropagation();\n void nativeImpact('medium');\n const native = await nativeConfirm({\n title: tr('projectCard.deleteTitle', 'Delete project'),\n message: tr('projectCard.deleteMessage', 'Are you sure you want to delete \"{{name}}\"?', {\n name: project.name,\n }),\n okButtonTitle: tr('common.delete', 'Delete'),\n cancelButtonTitle: tr('common.cancel', 'Cancel'),\n });\n const ok =\n native === null\n ? confirm(\n tr('projectCard.deleteMessage', 'Are you sure you want to delete \"{{name}}\"?', {\n name: project.name,\n })\n )\n : native;\n if (ok) {\n deleteProject(project.id);\n }\n };\n\n const handleCardKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n };\n\n return (\n <>\n <GlassCard\n treatment=\"insight\"\n className={`cursor-pointer relative focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50 ${\n selected ? 'ring-2 ring-primary/25 border-primary/50' : ''\n }`}\n onClick={handleCardClick}\n onKeyDown={handleCardKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={tr('projectCard.openProjectAriaLabel', 'Open project {{name}}', {\n name: project.name,\n })}\n >\n <CardHeader>\n <div className=\"flex items-start justify-between\">\n <div className=\"flex items-center gap-3 flex-1\">\n {onToggleSelect && (\n <input\n type=\"checkbox\"\n checked={selected}\n onChange={(event) => onToggleSelect(event.target.checked)}\n onClick={(event) => event.stopPropagation()}\n aria-label={tr('projectCard.selectProjectAriaLabel', 'Select project {{name}}', {\n name: project.name,\n })}\n className=\"mt-1 size-4 rounded border-border-subtle text-primary\"\n />\n )}\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10 transition-colors\">\n <FolderKanban className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"truncate\">{project.name}</CardTitle>\n <p className=\"truncate text-sm text-text-secondary\">{project.key}</p>\n </div>\n </div>\n <DropdownMenu>\n <DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>\n <Button variant=\"ghost\" size=\"sm\" className=\"size-8 p-0\">\n <MoreVertical size={16} />\n <span className=\"sr-only\">\n {tr('projectCard.openActions', 'Open project actions')}\n </span>\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: [\n { label: tr('projectCard.keyLabel', 'Key'), value: project.key },\n {\n label: tr('projectCard.workspaceIdLabel', 'Workspace ID'),\n value: project.workspaceId,\n },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n {tr('projectCard.getId', 'Get ID')}\n </DropdownMenuItem>\n {canRename && (\n <DropdownMenuItem\n onClick={(e) => {\n e.stopPropagation();\n setShowRenameDialog(true);\n }}\n >\n <Pencil size={16} className=\"mr-2\" />\n {tr('projectCard.rename', 'Rename')}\n </DropdownMenuItem>\n )}\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived\n ? tr('projectCard.unarchive', 'Unarchive')\n : tr('projectCard.archive', 'Archive')}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n {tr('projectCard.delete', 'Delete')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n <Badge className={`border ${getProjectStatusClasses(project.status)}`}>\n {project.status}\n </Badge>\n {project.archived && (\n <Badge\n variant=\"outline\"\n className=\"border-workspace-archived-border text-workspace-archived-text\"\n >\n <Archive size={12} className=\"mr-1\" />\n {tr('projectCard.archivedLabel', 'Archived')}\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">\n {tr('projectCard.created', 'Created {{date}}', { date: formatDate(project.createdAt) })}\n </div>\n {projectTags.length > 0 && (\n <div\n className=\"flex flex-wrap gap-1.5\"\n aria-label={tr('projectCard.projectTagsAriaLabel', 'Project tags')}\n >\n {projectTags.slice(0, 4).map((tag) => (\n <span\n key={tag.tagId}\n {...(onTagClick\n ? {\n role: 'button',\n tabIndex: 0,\n onClick: (event: React.MouseEvent<HTMLSpanElement>) => {\n event.stopPropagation();\n onTagClick(tag);\n },\n onKeyDown: (event: React.KeyboardEvent<HTMLSpanElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n event.stopPropagation();\n onTagClick(tag);\n }\n },\n }\n : {})}\n >\n <TagBadge\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n size=\"sm\"\n className={onTagClick ? 'cursor-pointer' : undefined}\n />\n </span>\n ))}\n {projectTags.length > 4 && (\n <Badge variant=\"outline\" className=\"text-xs\">\n +{projectTags.length - 4}\n </Badge>\n )}\n </div>\n )}\n </CardContent>\n </GlassCard>\n <EntityIdModal control={idModal} />\n {showRenameDialog && (\n <RenameProjectDialog\n project={project}\n open={showRenameDialog}\n onOpenChange={setShowRenameDialog}\n />\n )}\n </>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProjectMemberPickerDialog.js","names":[],"sources":["../../src/components/ProjectMemberPickerDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Search, UserPlus } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n Badge,\n Button,\n Input,\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Skeleton,\n} from '@burdenoff/fe-libs/ui';\nimport { useAddProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useInfiniteWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useTr } from '../hooks/useTr';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport type { WorkspaceMember } from '../types';\n\nconst MEMBER_PAGE_SIZE = 100;\nconst MAX_AUTOLOADED_PROJECT_MEMBER_PAGES = 10;\n\nfunction getFallbackDisplay(member: WorkspaceMember): { name: string; email?: string } {\n const meta = member.context?.metadata;\n const name =\n typeof meta?.displayName === 'string'\n ? meta.displayName\n : typeof meta?.name === 'string'\n ? meta.name\n : member.userId;\n const email = typeof meta?.email === 'string' ? meta.email : undefined;\n return { name, email };\n}\n\nexport interface ProjectMemberPickerDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n projectId: string;\n projectName?: string;\n}\n\nexport function ProjectMemberPickerDialog({\n open,\n onOpenChange,\n workspaceId,\n projectId,\n projectName,\n}: ProjectMemberPickerDialogProps) {\n const tr = useTr();\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n const { mutate: addProjectMember, isPending: isAdding } = useAddProjectMember(workspaceId);\n const [searchValue, setSearchValue] = useState('');\n const [addingUserId, setAddingUserId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const {\n data: workspaceMembersPages,\n isLoading: isLoadingMembers,\n isError: isErrorWorkspaceMembers,\n error: workspaceMembersError,\n hasNextPage: hasMoreWorkspaceMembers,\n fetchNextPage: fetchNextWorkspaceMembersPage,\n isFetchingNextPage: isFetchingNextWorkspaceMembersPage,\n refetch: refetchWorkspaceMembers,\n } = useInfiniteWorkspaceMembers(workspaceId, MEMBER_PAGE_SIZE, open);\n\n const {\n data: projectMembersPages,\n isLoading: isLoadingProjectMembers,\n isError: isErrorProjectMembers,\n error: projectMembersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId, workspaceId, MEMBER_PAGE_SIZE, open);\n\n useEffect(() => {\n if (\n !open ||\n !hasMoreProjectMembers ||\n isFetchingNextProjectMembersPage ||\n isErrorProjectMembers ||\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES\n ) {\n return;\n }\n\n void fetchNextProjectMembersPage();\n }, [\n fetchNextProjectMembersPage,\n hasMoreProjectMembers,\n isErrorProjectMembers,\n isFetchingNextProjectMembersPage,\n open,\n projectMembersPages?.pages.length,\n ]);\n\n const workspaceMembers = useMemo(\n () => workspaceMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [workspaceMembersPages?.pages]\n );\n const existingUserIdSet = useMemo(\n () =>\n new Set(\n projectMembersPages?.pages.flatMap((page) => page.items.map((member) => member.userId)) ??\n []\n ),\n [projectMembersPages?.pages]\n );\n const totalWorkspaceMembers = workspaceMembersPages?.pages[0]?.total ?? workspaceMembers.length;\n const reachedProjectMemberAutoloadCap =\n (projectMembersPages?.pages.length ?? 0) >= MAX_AUTOLOADED_PROJECT_MEMBER_PAGES;\n const isAutoloadingProjectMembers =\n !isErrorProjectMembers && hasMoreProjectMembers && !reachedProjectMemberAutoloadCap;\n\n const actors = useMemo<ActorInput[]>(\n () => workspaceMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [workspaceMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const eligibleMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n\n return workspaceMembers.filter((member) => {\n if (existingUserIdSet.has(member.userId)) {\n return false;\n }\n\n if (!needle) {\n return true;\n }\n\n const profile = profileMap[member.userId];\n if (profile) {\n if (profile.displayName.toLowerCase().includes(needle)) return true;\n if (profile.email?.toLowerCase().includes(needle)) return true;\n }\n\n const fallback = getFallbackDisplay(member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [existingUserIdSet, profileMap, searchValue, workspaceMembers]);\n\n const handleAddMember = (userId: string) => {\n void nativeImpact('medium');\n setAddingUserId(userId);\n setError(null);\n\n addProjectMember(\n { projectId, userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setAddingUserId(null);\n onOpenChange(false);\n },\n onError: (mutationError) => {\n void nativeNotify('error');\n setAddingUserId(null);\n setError(\n mutationError instanceof Error\n ? mutationError.message\n : tr('projectMemberPickerDialog.addError', 'Failed to add member to project')\n );\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"z-[120] flex h-[min(85vh,760px)] flex-col overflow-hidden sm:max-w-[720px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('projectMemberPickerDialog.title', 'Add member to project')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'projectMemberPickerDialog.description',\n 'Add an existing workspace member to {{projectName}}. Only current workspace members are eligible.',\n {\n projectName:\n projectName ?? tr('projectMemberPickerDialog.thisProject', 'this project'),\n }\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <div className=\"space-y-4 overflow-hidden\">\n {error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{error}</span>\n </div>\n ) : null}\n\n {projectMembersError instanceof Error ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{projectMembersError.message}</span>\n </div>\n ) : null}\n\n {workspaceMembersError instanceof Error ? (\n <div className=\"space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <div className=\"flex items-center gap-2\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{workspaceMembersError.message}</span>\n </div>\n <Button variant=\"outline\" onClick={() => void refetchWorkspaceMembers()}>\n {tr('projectMemberPickerDialog.retryLoadingMembers', 'Retry Loading Members')}\n </Button>\n </div>\n ) : null}\n\n <div className=\"flex items-center gap-2\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n placeholder={tr(\n 'projectMemberPickerDialog.searchPlaceholder',\n 'Search workspace members'\n )}\n className=\"pl-9\"\n disabled={isAdding}\n />\n </div>\n <Badge variant=\"secondary\">\n {tr('projectMemberPickerDialog.eligibleCount', '{{count}} eligible', {\n count: hasMoreWorkspaceMembers\n ? `${eligibleMembers.length}+`\n : eligibleMembers.length,\n })}\n </Badge>\n </div>\n\n <div className=\"max-h-[480px] space-y-3 overflow-y-auto pr-1\">\n {isErrorWorkspaceMembers ? null : isLoadingMembers ||\n isLoadingProjectMembers ||\n isAutoloadingProjectMembers ||\n isFetchingNextProjectMembersPage ? (\n Array.from({ length: 5 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border bg-bg-surface p-4\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"mt-2 h-4 w-56\" />\n </div>\n ))\n ) : projectMembersError instanceof Error ? null : eligibleMembers.length === 0 ? (\n <div className=\"rounded-lg border bg-bg-surface p-8 text-center\">\n <UserPlus className=\"mx-auto mb-3 size-10 text-text-secondary\" />\n <h3 className=\"text-lg font-semibold\">\n {tr('projectMemberPickerDialog.emptyTitle', 'No eligible members found')}\n </h3>\n <p className=\"mt-2 text-sm text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.emptyDescription',\n 'All workspace members are already part of this project, or nothing matched your search.'\n )}\n </p>\n {reachedProjectMemberAutoloadCap ? (\n <p className=\"mt-3 text-xs text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.autoloadCapNote',\n 'Loaded the first {{count}} project members to avoid an excessively long blocking load.',\n { count: MAX_AUTOLOADED_PROJECT_MEMBER_PAGES * MEMBER_PAGE_SIZE }\n )}\n </p>\n ) : null}\n {hasMoreWorkspaceMembers ? (\n <div className=\"mt-4\">\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage\n ? tr('projectMemberPickerDialog.loadingMore', 'Loading more…')\n : tr('projectMemberPickerDialog.loadMore', 'Load More Members')}\n </Button>\n </div>\n ) : null}\n </div>\n ) : (\n <>\n {eligibleMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getFallbackDisplay(member);\n const isSubmitting = isAdding && addingUserId === member.userId;\n\n return (\n <div\n key={member.id}\n className=\"flex items-center justify-between gap-4 rounded-lg border bg-bg-surface p-4\"\n >\n <div className=\"min-w-0 flex-1\">\n {profile ? (\n <ActorIdentity profile={profile} variant=\"cell\" />\n ) : (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{fallback.name}</p>\n {fallback.email ? (\n <p className=\"truncate text-sm text-text-secondary\">\n {fallback.email}\n </p>\n ) : (\n <p className=\"truncate text-xs text-text-secondary\">\n {member.userId}\n </p>\n )}\n </div>\n )}\n </div>\n\n <Button\n size=\"sm\"\n onClick={() => handleAddMember(member.userId)}\n disabled={isAdding}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {isSubmitting\n ? tr('projectMemberPickerDialog.adding', 'Adding...')\n : tr('projectMemberPickerDialog.add', 'Add')}\n </Button>\n </div>\n );\n })}\n\n {hasMoreWorkspaceMembers ? (\n <div className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed bg-bg-sunken/20 p-4 text-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'projectMemberPickerDialog.showingCount',\n 'Showing {{shown}} of {{total}} workspace members.',\n {\n shown: workspaceMembers.length,\n total: totalWorkspaceMembers,\n }\n )}\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextWorkspaceMembersPage()}\n disabled={isFetchingNextWorkspaceMembersPage || isAdding}\n >\n {isFetchingNextWorkspaceMembersPage\n ? tr('projectMemberPickerDialog.loadingMore', 'Loading more…')\n : tr('projectMemberPickerDialog.loadMore', 'Load More Members')}\n </Button>\n </div>\n ) : null}\n </>\n )}\n </div>\n </div>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"RenameProjectDialog.js","names":[],"sources":["../../src/components/RenameProjectDialog.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport {\n ResponsiveDialog,\n ResponsiveDialogContent,\n ResponsiveDialogDescription,\n ResponsiveDialogHeader,\n ResponsiveDialogTitle,\n Button,\n} from '@burdenoff/fe-libs/ui';\nimport {\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n FormDescription,\n Input,\n useForm,\n FormProvider,\n z,\n} from '@burdenoff/fe-libs/form';\n\nimport { useUpdateProject } from '../hooks/useProjectMutations';\nimport { useTr } from '../hooks/useTr';\n\nfunction renameProjectSchema(tr: ReturnType<typeof useTr>) {\n return z.object({\n // `.trim()` first so length validation runs on the trimmed value — otherwise a\n // padded name like \" a \" passes min(2) but submits as the 1-char \"a\".\n name: z\n .string()\n .trim()\n .min(2, tr('renameProjectDialog.validation.nameMin', 'Name must be at least 2 characters'))\n .max(\n 100,\n tr('renameProjectDialog.validation.nameMax', 'Name must be less than 100 characters')\n ),\n });\n}\n\ntype RenameProjectFormData = z.infer<ReturnType<typeof renameProjectSchema>>;\n\ninterface RenameProjectDialogProps {\n project: { id: string; name: string; workspaceId: string };\n open: boolean;\n onOpenChange: (open: boolean) => void;\n}\n\nexport function RenameProjectDialog({ project, open, onOpenChange }: RenameProjectDialogProps) {\n const tr = useTr();\n const { mutate: updateProject, isPending } = useUpdateProject(project.workspaceId);\n const [submitError, setSubmitError] = useState<string | null>(null);\n\n const schema = useMemo(() => renameProjectSchema(tr), [tr]);\n\n // This component is mounted only while open (the parent gates it with\n // `{open && <RenameProjectDialog .../>}`), so `defaultValues` always reflects\n // the current name on each open — no prop-syncing effect needed.\n const form = useForm({\n schema,\n defaultValues: { name: project.name },\n });\n\n const currentName = form.watch('name');\n const isUnchanged = currentName.trim() === project.name.trim();\n\n const onSubmit = (data: RenameProjectFormData) => {\n const trimmed = data.name.trim();\n if (trimmed === project.name.trim()) {\n onOpenChange(false);\n return;\n }\n\n setSubmitError(null);\n updateProject(\n { id: project.id, input: { name: trimmed } },\n {\n onSuccess: () => {\n onOpenChange(false);\n },\n onError: (error) => {\n setSubmitError(\n error instanceof Error\n ? error.message\n : tr('renameProjectDialog.submitError', 'Failed to rename project')\n );\n },\n }\n );\n };\n\n return (\n <ResponsiveDialog open={open} onOpenChange={onOpenChange}>\n <ResponsiveDialogContent className=\"sm:max-w-[500px]\">\n <ResponsiveDialogHeader>\n <ResponsiveDialogTitle>\n {tr('renameProjectDialog.title', 'Rename Project')}\n </ResponsiveDialogTitle>\n <ResponsiveDialogDescription>\n {tr(\n 'renameProjectDialog.description',\n 'Give this project a new name. The project key stays the same.'\n )}\n </ResponsiveDialogDescription>\n </ResponsiveDialogHeader>\n\n <FormProvider {...form}>\n <form onSubmit={form.handleSubmit(onSubmit)} className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>{tr('renameProjectDialog.nameLabel', 'Project Name')}</FormLabel>\n <FormControl>\n <Input\n {...field}\n placeholder={tr('renameProjectDialog.namePlaceholder', 'My Project')}\n />\n </FormControl>\n <FormDescription>\n {tr('renameProjectDialog.nameHint', 'A descriptive name for your project')}\n </FormDescription>\n <FormMessage />\n </FormItem>\n )}\n />\n\n {submitError && (\n <p className=\"text-sm text-status-error-text\" role=\"alert\">\n {submitError}\n </p>\n )}\n\n <div className=\"flex justify-end gap-3 pt-4\">\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n disabled={isPending}\n >\n {tr('common.cancel', 'Cancel')}\n </Button>\n <Button type=\"submit\" disabled={isPending || isUnchanged}>\n {isPending\n ? tr('renameProjectDialog.saving', 'Saving...')\n : tr('renameProjectDialog.save', 'Save')}\n </Button>\n </div>\n </form>\n </FormProvider>\n </ResponsiveDialogContent>\n </ResponsiveDialog>\n );\n}\n"],"mappings":";;;;;;;AAyBA,SAAS,EAAoB,GAA8B;AACzD,QAAO,EAAE,OAAO,EAGd,MAAM,EACH,QAAQ,CACR,MAAM,CACN,IAAI,GAAG,EAAG,0CAA0C,qCAAqC,CAAC,CAC1F,IACC,KACA,EAAG,0CAA0C,wCAAwC,CACtF,EACJ,CAAC;;AAWJ,SAAgB,EAAoB,EAAE,YAAS,SAAM,mBAA0C;CAC7F,IAAM,IAAK,GAAO,EACZ,EAAE,QAAQ,GAAe,iBAAc,EAAiB,EAAQ,YAAY,EAC5E,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAO7D,IAAO,EAAQ;EACnB,QANa,QAAc,EAAoB,EAAG,EAAE,CAAC,EAAG,CAAC;EAOzD,eAAe,EAAE,MAAM,EAAQ,MAAM;EACtC,CAAC,EAGI,IADc,EAAK,MAAM,OAAO,CACN,MAAM,KAAK,EAAQ,KAAK,MAAM,EAExD,KAAY,MAAgC;EAChD,IAAM,IAAU,EAAK,KAAK,MAAM;AAChC,MAAI,MAAY,EAAQ,KAAK,MAAM,EAAE;AACnC,KAAa,GAAM;AACnB;;AAIF,EADA,EAAe,KAAK,EACpB,EACE;GAAE,IAAI,EAAQ;GAAI,OAAO,EAAE,MAAM,GAAS;GAAE,EAC5C;GACE,iBAAiB;AACf,MAAa,GAAM;;GAErB,UAAU,MAAU;AAClB,MACE,aAAiB,QACb,EAAM,UACN,EAAG,mCAAmC,2BAA2B,CACtE;;GAEJ,CACF;;AAGH,QACE,kBAAC,GAAD;EAAwB;EAAoB;YAC1C,kBAAC,GAAD;GAAyB,WAAU;aAAnC,CACE,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,GAAD,EAAA,UACG,EAAG,6BAA6B,iBAAiB,EAC5B,CAAA,EACxB,kBAAC,GAAD,EAAA,UACG,EACC,mCACA,gEACD,EAC2B,CAAA,CACP,EAAA,CAAA,EAEzB,kBAAC,GAAD;IAAc,GAAI;cAChB,kBAAC,QAAD;KAAM,UAAU,EAAK,aAAa,EAAS;KAAE,WAAU;eAAvD;MACE,kBAAC,GAAD;OACE,SAAS,EAAK;OACd,MAAK;OACL,SAAS,EAAE,eACT,kBAAC,GAAD,EAAA,UAAA;QACE,kBAAC,GAAD,EAAA,UAAY,EAAG,iCAAiC,eAAe,EAAa,CAAA;QAC5E,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SACE,GAAI;SACJ,aAAa,EAAG,uCAAuC,aAAa;SACpE,CAAA,EACU,CAAA;QACd,kBAAC,GAAD,EAAA,UACG,EAAG,gCAAgC,sCAAsC,EAC1D,CAAA;QAClB,kBAAC,GAAD,EAAe,CAAA;QACN,EAAA,CAAA;OAEb,CAAA;MAED,KACC,kBAAC,KAAD;OAAG,WAAU;OAAiC,MAAK;iBAChD;OACC,CAAA;MAGN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,MAAK;QACL,SAAQ;QACR,eAAe,EAAa,GAAM;QAClC,UAAU;kBAET,EAAG,iBAAiB,SAAS;QACvB,CAAA,EACT,kBAAC,GAAD;QAAQ,MAAK;QAAS,UAAU,KAAa;kBAC1C,IACG,EAAG,8BAA8B,YAAY,GAC7C,EAAG,4BAA4B,OAAO;QACnC,CAAA,CACL;;MACD;;IACM,CAAA,CACS;;EACT,CAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspaceCard.js","names":[],"sources":["../../src/components/WorkspaceCard.tsx"],"sourcesContent":["import { Building2, Users, FolderKanban } from 'lucide-react';\nimport { useNavigate } from 'react-router';\nimport { GlassCard, CardHeader, CardTitle, CardContent, Badge } from '@burdenoff/fe-libs/ui';\nimport { useActiveContext } from '@burdenoff/fe-libs/shared/providers/shell';\nimport type { Workspace } from '../types';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { getWorkspaceStatusClasses } from '../utils/tokenHelpers';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useTr } from '../hooks/useTr';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n}\n\nexport function WorkspaceCard({ workspace }: WorkspaceCardProps) {\n const tr = useTr();\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { setOrganization, setWorkspace, setProject, setTenant } = useActiveContext();\n\n const openWorkspace = () => {\n // Drilling into a workspace COMMITS it as the active selection (BOFF-2821 G1):\n // the workspace token + `x-workspace-id` follow the committed selection, so\n // without this the detail page at `/workspaces/<this>` would render with the\n // *previously selected* workspace's token and 500. Setting the selection also\n // re-issues the token (via the header's context-sync effects) and syncs the\n // `?workspace=…` query; the path is the authority for the page itself.\n // The list is populated from the global `myWorkspaces` query, whose items\n // carry `organizationId`; the local Workspace type still names the field\n // `orgId`. Read both so the org commits regardless of the source query.\n const organizationId =\n (workspace as { organizationId?: string }).organizationId ?? workspace.orgId;\n if (organizationId) {\n setOrganization(organizationId);\n }\n setWorkspace(workspace.id);\n setProject(null);\n if (typeof workspace.tenantId === 'string') {\n setTenant(workspace.tenantId);\n }\n navigate(withCurrentSearch(`${basePath}/${workspace.id}`));\n };\n\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n openWorkspace();\n }\n };\n\n return (\n <GlassCard\n treatment=\"insight\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={openWorkspace}\n onKeyDown={handleKeyDown}\n role=\"button\"\n tabIndex={0}\n aria-label={tr('workspaceCard.openWorkspaceAriaLabel', 'Open workspace {{name}}', {\n name: workspace.name,\n })}\n data-tour=\"workspaces-card\"\n >\n <CardHeader>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10 transition-colors\">\n <Building2 className=\"text-primary\" size={24} />\n </div>\n <div className=\"flex-1 min-w-0\">\n <CardTitle className=\"text-lg truncate\">{workspace.name}</CardTitle>\n <div className=\"flex items-center gap-2 mt-1\">\n <Badge className={`text-xs border ${getWorkspaceStatusClasses(workspace.status)}`}>\n {workspace.status}\n </Badge>\n </div>\n </div>\n </div>\n </CardHeader>\n <CardContent>\n <div className=\"flex flex-wrap gap-4 text-sm text-text-secondary\">\n {typeof workspace.memberCount === 'number' && workspace.memberCount > 0 && (\n <div className=\"flex items-center gap-1\">\n <Users size={16} aria-hidden=\"true\" />\n <span>\n {tr('workspaceCard.memberCount', '{{count}} member{{suffix}}', {\n count: workspace.memberCount,\n suffix: workspace.memberCount === 1 ? '' : 's',\n })}\n </span>\n </div>\n )}\n {typeof workspace.projectCount === 'number' && workspace.projectCount > 0 && (\n <div className=\"flex items-center gap-1\">\n <FolderKanban size={16} aria-hidden=\"true\" />\n <span>\n {tr('workspaceCard.projectCount', '{{count}} project{{suffix}}', {\n count: workspace.projectCount,\n suffix: workspace.projectCount === 1 ? '' : 's',\n })}\n </span>\n </div>\n )}\n </div>\n {workspace.domain && (\n <p className=\"mt-2 truncate text-xs text-text-muted\">{workspace.domain}</p>\n )}\n </CardContent>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;AAcA,SAAgB,EAAc,EAAE,gBAAiC;CAC/D,IAAM,IAAK,GAAO,EACZ,IAAW,GAAa,EACxB,EAAE,cAAW,QAAQ,GAAsB,EAC3C,EAAE,yBAAsB,GAAqB,EAC7C,EAAE,oBAAiB,iBAAc,eAAY,iBAAc,GAAkB,EAE7E,UAAsB;EAU1B,IAAM,IACH,EAA0C,kBAAkB,EAAU;AASzE,EARI,KACF,EAAgB,EAAe,EAEjC,EAAa,EAAU,GAAG,EAC1B,EAAW,KAAK,EACZ,OAAO,EAAU,YAAa,YAChC,EAAU,EAAU,SAAS,EAE/B,EAAS,EAAkB,GAAG,EAAS,GAAG,EAAU,KAAK,CAAC;;AAU5D,QACE,kBAAC,GAAD;EACE,WAAU;EACV,WAAU;EACV,SAAS;EACT,YAZmB,MAA+C;AACpE,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAe;;EAUf,MAAK;EACL,UAAU;EACV,cAAY,EAAG,wCAAwC,2BAA2B,EAChF,MAAM,EAAU,MACjB,CAAC;EACF,aAAU;YAVZ,CAYE,kBAAC,GAAD,EAAA,UACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KAAW,WAAU;KAAe,MAAM;KAAM,CAAA;IAC5C,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAW,WAAU;eAAoB,EAAU;KAAiB,CAAA,EACpE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAO,WAAW,kBAAkB,EAA0B,EAAU,OAAO;gBAC5E,EAAU;MACL,CAAA;KACJ,CAAA,CACF;MACF;MACK,CAAA,EACb,kBAAC,GAAD,EAAA,UAAA,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,OAAO,EAAU,eAAgB,YAAY,EAAU,cAAc,KACpE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAO,MAAM;KAAI,eAAY;KAAS,CAAA,EACtC,kBAAC,QAAD,EAAA,UACG,EAAG,6BAA6B,8BAA8B;KAC7D,OAAO,EAAU;KACjB,QAAQ,EAAU,gBAAgB,IAAI,KAAK;KAC5C,CAAC,EACG,CAAA,CACH;OAEP,OAAO,EAAU,gBAAiB,YAAY,EAAU,eAAe,KACtE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD;KAAc,MAAM;KAAI,eAAY;KAAS,CAAA,EAC7C,kBAAC,QAAD,EAAA,UACG,EAAG,8BAA8B,+BAA+B;KAC/D,OAAO,EAAU;KACjB,QAAQ,EAAU,iBAAiB,IAAI,KAAK;KAC7C,CAAC,EACG,CAAA,CACH;MAEJ;MACL,EAAU,UACT,kBAAC,KAAD;GAAG,WAAU;aAAyC,EAAU;GAAW,CAAA,CAEjE,EAAA,CAAA,CACJ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspaceContextPathSync.js","names":[],"sources":["../../src/components/WorkspaceContextPathSync.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\n\n// First path segments that are NOT a `:workspaceId` — the unscoped, context-driven\n// routes (see WorkspacesRoutes). When the URL starts with one of these there is no\n// workspace segment to keep in sync.\nconst UNSCOPED_FIRST_SEGMENTS = new Set([\n 'list',\n 'new',\n 'invites',\n 'invite',\n 'projects',\n 'members',\n 'activity',\n]);\n\n/**\n * Keeps the workspace-scoped URL (`/workspaces/:workspaceId/...`) in step with the\n * active context.\n *\n * The route path `:workspaceId` intentionally OVERRIDES the active context for data\n * fetching (deep-link / drill into a specific workspace). The hazard is that the\n * chrome context switcher updates the active context (+ `?workspace=` query +\n * storage) but cannot rewrite this MFE's route path — so after a switch the path\n * segment goes stale and its override silently points the page at the previously\n * viewed workspace (wrong project list, create lands in the old workspace, etc.;\n * BOFF-2805).\n *\n * Fix: when (and only when) the active context workspace CHANGES, rewrite the path's\n * workspace segment to the new context. Firing on change — not on mount — preserves\n * deep-links and drill-in (those change the path, not the context, so they are left\n * alone and their override stands). Nested resource ids (`/projects/:projectId/...`)\n * are dropped because they belong to the old workspace; we land on the page-level\n * route for the new workspace. The query string (`?workspace=…&org=…`) is preserved.\n *\n * One-directional (context → path); the path never writes back to context, so there\n * is no feedback loop.\n */\nexport function WorkspaceContextPathSync() {\n const { workspaceId: contextWorkspaceId, basePath = '/workspaces' } = useWorkspacesContext();\n const navigate = useNavigate();\n const previousContextWorkspaceIdRef = useRef(contextWorkspaceId);\n\n useEffect(() => {\n const previousContextWorkspaceId = previousContextWorkspaceIdRef.current;\n previousContextWorkspaceIdRef.current = contextWorkspaceId;\n\n // Only react to an actual context switch — never on initial mount or deep-link\n // (those must keep whatever workspace the URL already points at). The active\n // context workspace is the sole trigger, so it is the only dependency; the\n // current URL is read inside the effect (not a dependency) — it is mutable\n // browser state, not a reactive value (react-doctor/no-mutable-in-deps).\n if (previousContextWorkspaceId === contextWorkspaceId) return;\n if (!contextWorkspaceId) return;\n // Ignore the FIRST hydration (empty -> first resolved workspace). The active\n // context resolves asynchronously from URL/storage, so the initial\n // `undefined -> <stored>` transition is NOT a user-initiated switch — treating\n // it as one would rewrite a scoped deep-link (`/workspaces/<linked>/projects`\n // loaded with a different stored workspace) to the stored one. Only a real\n // workspace -> different workspace is a switch.\n if (!previousContextWorkspaceId) return;\n if (typeof window === 'undefined') return;\n\n const { pathname, search } = window.location;\n const cleanBase = basePath.endsWith('/') ? basePath.slice(0, -1) : basePath;\n if (pathname !== cleanBase && !pathname.startsWith(`${cleanBase}/`)) {\n return;\n }\n\n const segments = pathname.slice(cleanBase.length).split('/').filter(Boolean);\n\n // Unscoped / context-driven route — nothing to rewrite.\n if (segments.length === 0 || UNSCOPED_FIRST_SEGMENTS.has(segments[0]!)) return;\n\n // segments[0] is a `:workspaceId`. Already aligned → done.\n if (segments[0] === contextWorkspaceId) return;\n\n // Rewrite the workspace segment to the new context, keep the page-type segment\n // (projects / members / activity / settings / invite), drop any nested resource\n // ids that belong to the old workspace. The query string (`?workspace=…&org=…`)\n // is preserved; the host ActiveContextProvider syncs `?workspace=` to the new\n // context right after, so the URL settles to path == ?workspace == context.\n const pageTypeSegment = segments[1];\n const nextSegments = [contextWorkspaceId, pageTypeSegment].filter(Boolean);\n const nextPath = `${cleanBase}/${nextSegments.join('/')}${search}`;\n\n if (nextPath !== `${pathname}${search}`) {\n navigate(nextPath, { replace: true });\n }\n }, [contextWorkspaceId, basePath, navigate]);\n\n return null;\n}\n"],"mappings":";;;;AAOA,IAAM,IAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACA;CACD,CAAC;AAwBF,SAAgB,IAA2B;CACzC,IAAM,EAAE,aAAa,GAAoB,cAAW,kBAAkB,GAAsB,EACtF,IAAW,GAAa,EACxB,IAAgC,EAAO,EAAmB;AAkDhE,QAhDA,QAAgB;EACd,IAAM,IAA6B,EAA8B;AAiBjE,MAhBA,EAA8B,UAAU,GAOpC,MAA+B,KAC/B,CAAC,KAOD,CAAC,KACD,OAAO,SAAW,IAAa;EAEnC,IAAM,EAAE,aAAU,cAAW,OAAO,UAC9B,IAAY,EAAS,SAAS,IAAI,GAAG,EAAS,MAAM,GAAG,GAAG,GAAG;AACnE,MAAI,MAAa,KAAa,CAAC,EAAS,WAAW,GAAG,EAAU,GAAG,CACjE;EAGF,IAAM,IAAW,EAAS,MAAM,EAAU,OAAO,CAAC,MAAM,IAAI,CAAC,OAAO,QAAQ;AAM5E,MAHI,EAAS,WAAW,KAAK,EAAwB,IAAI,EAAS,GAAI,IAGlE,EAAS,OAAO,EAAoB;EASxC,IAAM,IAAW,GAAG,EAAU,GADT,CAAC,GADE,EAAS,GACyB,CAAC,OAAO,QAAQ,CAC5B,KAAK,IAAI,GAAG;AAE1D,EAAI,MAAa,GAAG,IAAW,OAC7B,EAAS,GAAU,EAAE,SAAS,IAAM,CAAC;IAEtC;EAAC;EAAoB;EAAU;EAAS,CAAC,EAErC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"WorkspacesErrorBoundary.js","names":[],"sources":["../../src/components/WorkspacesErrorBoundary.tsx"],"sourcesContent":["import { Component, type ReactNode } from 'react';\nimport { AlertCircle, RefreshCw, Home } from 'lucide-react';\nimport { Button } from '@burdenoff/fe-libs/ui';\nimport { useTr } from '../hooks/useTr';\n\ninterface Props {\n children: ReactNode;\n fallback?: ReactNode;\n onReset?: () => void;\n /**\n * Optional \"go home\" handler. The boundary is a class component (can't\n * call `useNavigate`), so the host shell injects a React-Router-aware\n * handler here. Falls back to `window.location.href = '/'` when omitted\n * so a host that hasn't wired this up still gets a working button.\n */\n onGoHome?: () => void;\n /**\n * Contact-support email shown in the fallback UI. Defaults to\n * `support@burdenoff.com`; hosts can override (e.g. for a tenant-branded\n * deployment).\n */\n supportEmail?: string;\n}\n\ninterface ClassProps extends Props {\n tr: ReturnType<typeof useTr>;\n}\n\ninterface State {\n hasError: boolean;\n error: Error | null;\n errorInfo: React.ErrorInfo | null;\n}\n\n/**\n * Internal class boundary that receives the translator via props.\n * The public `WorkspacesErrorBoundary` wrapper is a functional component\n * so it can call `useTr()`.\n */\nclass WorkspacesErrorBoundaryClass extends Component<ClassProps, State> {\n constructor(props: ClassProps) {\n super(props);\n this.state = {\n hasError: false,\n error: null,\n errorInfo: null,\n };\n }\n\n static getDerivedStateFromError(error: Error): Partial<State> {\n return { hasError: true, error };\n }\n\n componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {\n // Log error to console in development\n if (process.env.NODE_ENV === 'development') {\n console.error('Workspaces Error Boundary caught an error:', error, errorInfo);\n }\n\n // Update state with error details\n this.setState({\n error,\n errorInfo,\n });\n\n // Forward to the shell-owned event bus per\n // ~/products/infra/infra-specs/microfrontends/47-FRONTEND-EVENT-BUS.md.\n // The shell wires Rybbit / OTel / SRE sinks; the MFE never sinks directly.\n // Window guard keeps SSR / non-browser environments safe.\n if (typeof window !== 'undefined') {\n const w = window as unknown as {\n __burdenoffEventBus?: { emit: (name: string, payload: unknown) => void };\n };\n try {\n w.__burdenoffEventBus?.emit('workspaces.error-boundary.caught', {\n source: 'microfe-workspaces',\n message: error.message,\n name: error.name,\n stack: error.stack,\n componentStack: errorInfo.componentStack,\n });\n } catch {\n // Bus failures must not mask the original render error.\n }\n }\n }\n\n handleReset = () => {\n this.setState({\n hasError: false,\n error: null,\n errorInfo: null,\n });\n\n // Call optional onReset callback\n this.props.onReset?.();\n };\n\n handleReload = () => {\n // Deliberately a full-page reload here: the React tree threw and is\n // already corrupted, so SPA navigation can't be trusted. This is the\n // one window.location call we keep in the boundary.\n window.location.reload();\n };\n\n handleGoHome = () => {\n if (this.props.onGoHome) {\n this.props.onGoHome();\n // SPA navigation doesn't remount this boundary; clear error state so\n // the new route renders instead of the fallback UI.\n this.setState({ hasError: false, error: null, errorInfo: null });\n return;\n }\n window.location.href = '/';\n };\n\n render() {\n const { tr } = this.props;\n if (this.state.hasError) {\n // Use custom fallback if provided\n if (this.props.fallback) {\n return this.props.fallback;\n }\n\n // Default error UI\n return (\n <div className=\"flex items-center justify-center min-h-[400px] p-6\">\n <div className=\"max-w-md w-full text-center space-y-6\">\n {/* Error Icon */}\n <div className=\"flex justify-center\">\n <div className=\"rounded-full bg-status-error-bg/10 p-6\">\n <AlertCircle className=\"size-12 text-status-error-text\" />\n </div>\n </div>\n\n {/* Error Message */}\n <div className=\"space-y-2\">\n <h2 className=\"text-2xl font-bold\">\n {tr('errorBoundary.title', 'Something went wrong')}\n </h2>\n <p className=\"text-text-secondary\">\n {tr(\n 'errorBoundary.description',\n 'We encountered an unexpected error. Please try refreshing the page or contact support if the problem persists.'\n )}\n </p>\n </div>\n\n {/* Error Details (Development Only) */}\n {process.env.NODE_ENV === 'development' && this.state.error && (\n <details className=\"text-left\">\n <summary className=\"cursor-pointer text-sm font-medium text-text-secondary hover:text-text-primary transition-colors duration-200\">\n {tr('errorBoundary.errorDetails', 'Error Details')}\n </summary>\n <div className=\"mt-2 p-4 bg-bg-sunken rounded-lg text-xs font-mono overflow-auto max-h-40\">\n <p className=\"font-semibold text-status-error-text\">{this.state.error.name}:</p>\n <p className=\"text-text-primary\">{this.state.error.message}</p>\n {this.state.error.stack && (\n <pre className=\"mt-2 text-text-secondary text-xs overflow-x-auto\">\n {this.state.error.stack}\n </pre>\n )}\n </div>\n </details>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 justify-center\">\n <Button onClick={this.handleReset} variant=\"default\">\n <RefreshCw className=\"mr-2 size-4\" />\n {tr('errorBoundary.tryAgain', 'Try Again')}\n </Button>\n <Button onClick={this.handleReload} variant=\"outline\">\n {tr('errorBoundary.reloadPage', 'Reload Page')}\n </Button>\n <Button onClick={this.handleGoHome} variant=\"ghost\">\n <Home className=\"mr-2 size-4\" />\n {tr('errorBoundary.goHome', 'Go Home')}\n </Button>\n </div>\n\n {/* Support Link */}\n <p className=\"text-sm text-text-secondary\">\n {tr('errorBoundary.needHelp', 'Need help?')}{' '}\n <a\n href={`mailto:${this.props.supportEmail ?? 'support@burdenoff.com'}`}\n className=\"text-primary hover:underline transition-colors duration-200\"\n >\n {tr('errorBoundary.contactSupport', 'Contact Support')}\n </a>\n </p>\n </div>\n </div>\n );\n }\n\n return this.props.children;\n }\n}\n\n/**\n * Error Boundary component for Workspaces microfrontend.\n * Catches and handles runtime errors gracefully.\n *\n * The public export is a thin functional wrapper around the class boundary\n * so it can consume the i18n translator via `useTr()`.\n */\nexport function WorkspacesErrorBoundary(props: Props) {\n const tr = useTr();\n return <WorkspacesErrorBoundaryClass {...props} tr={tr} />;\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"ProjectTagsWidget.js","names":[],"sources":["../../../src/components/tags/ProjectTagsWidget.tsx"],"sourcesContent":["/**\n * ProjectTagsWidget\n *\n * Local replacement for `@burdenoff/fe-libs/tag` `TagsWidget`, used until\n * fe-libs ships a fix for the non-clickable `AddTagTrigger` (the published\n * trigger swallows the props Radix Slot tries to forward via\n * `<PopoverTrigger asChild>`, so the popover never opens).\n *\n * This component bypasses the broken trigger by rendering a regular\n * `<PopoverTrigger>` (no `asChild`) so Radix mounts the click-handling\n * button itself. Internals (search, create, assign, remove) mirror the\n * fe-libs `TagsWidget` so the data path through wspace-tags-svc is\n * identical.\n *\n * TODO(fe-libs): once `@burdenoff/fe-libs/tag` `AddTagTrigger` forwards\n * props/ref to the underlying button, delete this file and switch back\n * to `<TagsWidget>` from `@burdenoff/fe-libs/tag`.\n */\n\nimport { gql, type ApolloClient } from '@apollo/client';\nimport { useLazyQuery, useMutation } from '@apollo/client/react';\nimport { Loader2, Plus, Tag as TagIcon, X } from 'lucide-react';\nimport { useCallback, useEffect, useRef, useState } from 'react';\n\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { cn } from '@burdenoff/fe-libs/shared/utils';\nimport { useTr } from '../../hooks/useTr';\n\n// --- GraphQL ----------------------------------------------------------------\n\ninterface VocabResult {\n id: string;\n key: string;\n name: string;\n}\n\nconst SEARCH_TAGS = gql`\n query ProjectTagsWidget_SearchTags($query: String!, $vocabularyId: ID, $limit: Int) {\n searchTags(query: $query, vocabularyId: $vocabularyId, limit: $limit) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n description\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst GET_VOCABULARY_BY_KEY = gql`\n query ProjectTagsWidget_GetVocabularyByKey($key: String!) {\n getVocabularyByKey(key: $key) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_VOCABULARY = gql`\n mutation ProjectTagsWidget_CreateVocabulary($input: CreateVocabularyInput!) {\n createVocabulary(input: $input) {\n id\n key\n name\n }\n }\n`;\n\nconst CREATE_TAG = gql`\n mutation ProjectTagsWidget_CreateTag($input: CreateTagInput!) {\n createTag(input: $input) {\n id\n key\n label\n value\n vocabularyId\n parentId\n hierarchyPath\n color\n icon\n vocabulary {\n key\n }\n }\n }\n`;\n\nconst DEFAULT_VOCAB_KEY = 'general';\n\nfunction isTagItem(value: unknown): value is TagItem {\n if (!value || typeof value !== 'object') return false;\n const tag = value as Partial<Record<keyof TagItem, unknown>>;\n return typeof tag.id === 'string' && typeof tag.key === 'string' && typeof tag.label === 'string';\n}\n\n// --- Props ------------------------------------------------------------------\n\nexport interface ProjectTagsWidgetProps {\n /** Entity type recorded on the assigned-tag projection (e.g. \"Project\"). */\n entityType: string;\n /** Entity id (project id). */\n entityId: string;\n /** Workspace scope — widget renders nothing when empty. */\n workspaceId?: string;\n /** Controlled tag list owned by the parent. */\n tags: TagItem[];\n /** Persist a newly assigned tag on the entity. */\n onAdd: (tag: TagItem) => void | Promise<void>;\n /** Persist a tag removal on the entity. */\n onRemove: (tagId: string) => void | Promise<void>;\n /** Allow creating new tags on the fly. Default: true. */\n allowCreate?: boolean;\n /** Cap on assigned tags. Default 20. */\n maxTags?: number;\n className?: string;\n}\n\n// --- Component --------------------------------------------------------------\n\nexport function ProjectTagsWidget({\n entityType: _entityType,\n entityId,\n workspaceId,\n tags,\n onAdd,\n onRemove,\n allowCreate = true,\n maxTags = 20,\n className,\n}: ProjectTagsWidgetProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [createError, setCreateError] = useState<string | null>(null);\n const [adding, setAdding] = useState(false);\n const [resolvedVocabId, setResolvedVocabId] = useState<string | undefined>();\n const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Bun may install Apollo twice when fe-libs is consumed as a package; the runtime client is compatible.\n const wsClient = useGatewayClient('workspace') as unknown as ApolloClient;\n\n const skip = !workspaceId || !entityId;\n\n const assignedTagIds = new Set(tags.map((t) => t.id));\n const canAddMore = tags.length < maxTags;\n\n const [runSearch, { data: searchData, loading: searching }] = useLazyQuery<{\n searchTags: TagItem[];\n }>(SEARCH_TAGS, { client: wsClient });\n\n const searchResults = (searchData?.searchTags ?? []).filter(\n (tag): tag is TagItem => isTagItem(tag) && !assignedTagIds.has(tag.id)\n );\n\n const [createTag, { loading: creating }] = useMutation<{\n createTag: TagItem;\n }>(CREATE_TAG, { client: wsClient });\n\n const [fetchVocabByKey] = useLazyQuery<{\n getVocabularyByKey: VocabResult | null;\n }>(GET_VOCABULARY_BY_KEY, { client: wsClient });\n\n const [createVocabulary] = useMutation<{\n createVocabulary: VocabResult;\n }>(CREATE_VOCABULARY, { client: wsClient });\n\n const resolveVocabId = useCallback(async (): Promise<string> => {\n if (resolvedVocabId) return resolvedVocabId;\n\n const res = await fetchVocabByKey({ variables: { key: DEFAULT_VOCAB_KEY } });\n const existing = res.data?.getVocabularyByKey;\n if (existing) {\n setResolvedVocabId(existing.id);\n return existing.id;\n }\n\n const created = await createVocabulary({\n variables: {\n input: {\n key: DEFAULT_VOCAB_KEY,\n name: 'General',\n description: 'Default vocabulary for general-purpose tags',\n scope: 'WORKSPACE',\n allowCustomTags: true,\n },\n },\n });\n const newVocab = created.data?.createVocabulary;\n if (!newVocab)\n throw new Error(\n tr('projectTagsWidget.createVocabularyError', 'Failed to create default vocabulary')\n );\n setResolvedVocabId(newVocab.id);\n return newVocab.id;\n }, [resolvedVocabId, fetchVocabByKey, createVocabulary, tr]);\n\n const handleSearchChange = useCallback(\n (value: string) => {\n setSearch(value);\n if (createError) setCreateError(null);\n if (debounceRef.current) clearTimeout(debounceRef.current);\n if (!value.trim()) return;\n debounceRef.current = setTimeout(() => {\n void runSearch({\n variables: { query: value, vocabularyId: resolvedVocabId, limit: 10 },\n });\n }, 250);\n },\n [runSearch, resolvedVocabId, createError]\n );\n\n useEffect(() => {\n return () => {\n if (debounceRef.current) clearTimeout(debounceRef.current);\n };\n }, []);\n\n const handleAssign = useCallback(\n async (tag: TagItem) => {\n if (!canAddMore || adding) return;\n setAdding(true);\n try {\n const normalizedTag: TagItem = {\n ...tag,\n vocabularyKey: tag.vocabularyKey ?? tag.vocabulary?.key ?? null,\n };\n await onAdd(normalizedTag);\n setSearch('');\n setOpen(false);\n } finally {\n setAdding(false);\n }\n },\n [onAdd, canAddMore, adding]\n );\n\n const handleCreate = useCallback(async () => {\n if (!allowCreate || !search.trim() || creating) return;\n setCreateError(null);\n try {\n const label = search.trim();\n const key = label\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '');\n if (!key) {\n setCreateError(\n tr(\n 'projectTagsWidget.createValidationError',\n 'Must contain at least one letter or number'\n )\n );\n return;\n }\n const vocabId = await resolveVocabId();\n const res = await createTag({\n variables: { input: { key, label, vocabularyId: vocabId } },\n });\n const created = res.data?.createTag;\n if (!isTagItem(created)) throw new Error('No data returned');\n await handleAssign(created);\n } catch (err) {\n setCreateError(\n err instanceof Error\n ? err.message\n : tr('projectTagsWidget.createGenericError', 'Failed to create tag')\n );\n }\n }, [allowCreate, search, creating, resolveVocabId, createTag, handleAssign, tr]);\n\n const handleRemove = useCallback(\n async (tagId: string) => {\n await onRemove(tagId);\n },\n [onRemove]\n );\n\n if (skip) return null;\n\n const showCreateOption = Boolean(\n allowCreate && search.trim() && !searching && searchResults.length === 0\n );\n\n return (\n <div className={cn('flex flex-wrap items-center gap-1.5', className)}>\n {tags.map((tag) => (\n <TagBadge\n key={tag.id}\n id={tag.id}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => void handleRemove(tag.id)}\n size=\"sm\"\n />\n ))}\n\n {canAddMore && (\n <Popover open={open} onOpenChange={setOpen}>\n {/*\n Intentionally do NOT use `asChild` here — the fe-libs\n `AddTagTrigger` swallows the props Radix forwards, leaving the\n trigger non-clickable. Letting Radix render its own button\n restores the click handler.\n */}\n <PopoverTrigger\n type=\"button\"\n aria-label={tr('projectTagsWidget.addTagAriaLabel', 'Add tag')}\n className={cn(\n 'inline-flex items-center gap-1 px-2 py-0.5 rounded-full border border-dashed',\n 'border-border-subtle text-text-secondary text-xs',\n 'hover:border-foreground hover:text-text-primary transition-colors',\n 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)]'\n )}\n >\n <TagIcon className=\"size-3\" />\n <span>\n {tags.length > 0\n ? tr('projectTagsWidget.add', 'Add')\n : tr('projectTagsWidget.addTags', 'Add tags')}\n </span>\n </PopoverTrigger>\n\n <PopoverContent\n className=\"z-50 w-72 max-w-[calc(100vw-2rem)] border-border-subtle/80 bg-popover p-0 shadow-xl\"\n align=\"start\"\n side=\"bottom\"\n sideOffset={8}\n >\n <Command shouldFilter={false}>\n <div className=\"relative\">\n <CommandInput\n placeholder={tr(\n 'projectTagsWidget.searchPlaceholder',\n 'Search or create a tag...'\n )}\n value={search}\n onValueChange={handleSearchChange}\n className=\"h-10 border-0 bg-transparent pr-9 text-sm outline-none placeholder:text-text-secondary\"\n />\n {searching && (\n <Loader2 className=\"absolute right-3 top-1/2 size-3.5 -translate-y-1/2 animate-spin text-text-secondary\" />\n )}\n </div>\n\n <CommandList className=\"max-h-48 overflow-y-auto\">\n {search.trim() === '' && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n {tr('projectTagsWidget.typeToSearch', 'Type to search or create a tag')}\n </CommandEmpty>\n )}\n\n {searchResults.length > 0 && (\n <CommandGroup>\n {searchResults.map((tag) => (\n <CommandItem\n key={tag.id}\n value={tag.id}\n onSelect={() => void handleAssign(tag)}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer\"\n disabled={adding}\n >\n {tag.icon && <span className=\"text-sm\">{tag.icon}</span>}\n <span\n className=\"truncate text-sm\"\n style={tag.color ? { color: tag.color } : undefined}\n >\n {tag.label}\n </span>\n {adding && <Loader2 className=\"ml-auto size-3 animate-spin\" />}\n </CommandItem>\n ))}\n </CommandGroup>\n )}\n\n {showCreateOption && (\n <CommandGroup>\n <CommandItem\n value={`__create__${search}`}\n onSelect={() => void handleCreate()}\n className=\"flex items-center gap-2 px-3 py-2 cursor-pointer text-primary\"\n disabled={creating}\n >\n {creating ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Plus className=\"size-3.5\" />\n )}\n <span className=\"text-sm truncate\">\n {creating\n ? tr('projectTagsWidget.creating', 'Creating...')\n : tr('projectTagsWidget.create', 'Create \"{{label}}\"', {\n label: search,\n })}\n </span>\n </CommandItem>\n </CommandGroup>\n )}\n\n {search.trim() !== '' &&\n !searching &&\n searchResults.length === 0 &&\n !showCreateOption && (\n <CommandEmpty className=\"py-4 text-center text-xs text-text-secondary\">\n {tr('projectTagsWidget.noTagsFound', 'No tags found')}\n </CommandEmpty>\n )}\n\n {createError && (\n <div className=\"px-3 py-2 text-xs text-status-error-text border-t border-border-subtle\">\n {createError}\n </div>\n )}\n </CommandList>\n\n {!canAddMore && (\n <div className=\"border-t border-border-subtle px-3 py-2 text-xs text-text-secondary flex items-center gap-1\">\n <X className=\"size-3\" />\n {tr('projectTagsWidget.maxTagsReached', 'Maximum {{maxTags}} tags reached', {\n maxTags,\n })}\n </div>\n )}\n </Command>\n </PopoverContent>\n </Popover>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAiDA,IAAM,IAAc,CAAG;;;;;;;;;;;;;;;;;;GAoBjB,IAAwB,CAAG;;;;;;;;GAU3B,KAAoB,CAAG;;;;;;;;GAUvB,IAAa,CAAG;;;;;;;;;;;;;;;;;GAmBhB,IAAoB;AAE1B,SAAS,EAAU,GAAkC;AACnD,KAAI,CAAC,KAAS,OAAO,KAAU,SAAU,QAAO;CAChD,IAAM,IAAM;AACZ,QAAO,OAAO,EAAI,MAAO,YAAY,OAAO,EAAI,OAAQ,YAAY,OAAO,EAAI,SAAU;;AA2B3F,SAAgB,EAAkB,EAChC,YAAY,GACZ,aACA,iBACA,SACA,UACA,aACA,iBAAc,IACd,aAAU,IACV,iBACyB;CACzB,IAAM,IAAK,GAAO,EACZ,CAAC,IAAM,KAAW,EAAS,GAAM,EACjC,CAAC,GAAQ,KAAa,EAAS,GAAG,EAClC,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAQ,KAAa,EAAS,GAAM,EACrC,CAAC,GAAiB,KAAsB,GAA8B,EACtE,IAAc,EAA6C,KAAK,EAGhE,IAAW,GAAiB,YAAY,EAExC,IAAO,CAAC,MAAe,CAAC,GAExB,KAAiB,IAAI,IAAI,EAAK,KAAK,MAAM,EAAE,GAAG,CAAC,EAC/C,IAAa,EAAK,SAAS,GAE3B,CAAC,GAAW,EAAE,MAAM,IAAY,SAAS,OAAe,EAE3D,GAAa,EAAE,QAAQ,GAAU,CAAC,EAE/B,KAAiB,IAAY,cAAc,EAAE,EAAE,QAClD,MAAwB,EAAU,EAAI,IAAI,CAAC,GAAe,IAAI,EAAI,GAAG,CACvE,EAEK,CAAC,GAAW,EAAE,SAAS,OAAc,EAExC,GAAY,EAAE,QAAQ,GAAU,CAAC,EAE9B,CAAC,KAAmB,EAEvB,GAAuB,EAAE,QAAQ,GAAU,CAAC,EAEzC,CAAC,KAAoB,EAExB,IAAmB,EAAE,QAAQ,GAAU,CAAC,EAErC,IAAiB,EAAY,YAA6B;AAC9D,MAAI,EAAiB,QAAO;EAG5B,IAAM,KADM,MAAM,EAAgB,EAAE,WAAW,EAAE,KAAK,GAAmB,EAAE,CAAC,EACvD,MAAM;AAC3B,MAAI,EAEF,QADA,EAAmB,EAAS,GAAG,EACxB,EAAS;EAclB,IAAM,KAXU,MAAM,EAAiB,EACrC,WAAW,EACT,OAAO;GACL,KAAK;GACL,MAAM;GACN,aAAa;GACb,OAAO;GACP,iBAAiB;GAClB,EACF,EACF,CAAC,EACuB,MAAM;AAC/B,MAAI,CAAC,EACH,OAAU,MACR,EAAG,2CAA2C,sCAAsC,CACrF;AAEH,SADA,EAAmB,EAAS,GAAG,EACxB,EAAS;IACf;EAAC;EAAiB;EAAiB;EAAkB;EAAG,CAAC,EAEtD,KAAqB,GACxB,MAAkB;AACjB,IAAU,EAAM,EACZ,KAAa,EAAe,KAAK,EACjC,EAAY,WAAS,aAAa,EAAY,QAAQ,EACrD,EAAM,MAAM,KACjB,EAAY,UAAU,iBAAiB;AAChC,KAAU,EACb,WAAW;IAAE,OAAO;IAAO,cAAc;IAAiB,OAAO;IAAI,EACtE,CAAC;KACD,IAAI;IAET;EAAC;EAAW;EAAiB;EAAY,CAC1C;AAED,eACe;AACX,EAAI,EAAY,WAAS,aAAa,EAAY,QAAQ;IAE3D,EAAE,CAAC;CAEN,IAAM,IAAe,EACnB,OAAO,MAAiB;AAClB,SAAC,KAAc,IACnB;KAAU,GAAK;AACf,OAAI;AAOF,IAFA,MAAM,EAJyB;KAC7B,GAAG;KACH,eAAe,EAAI,iBAAiB,EAAI,YAAY,OAAO;KAC5D,CACyB,EAC1B,EAAU,GAAG,EACb,EAAQ,GAAM;aACN;AACR,MAAU,GAAM;;;IAGpB;EAAC;EAAO;EAAY;EAAO,CAC5B,EAEK,KAAe,EAAY,YAAY;AACvC,SAAC,KAAe,CAAC,EAAO,MAAM,IAAI,IACtC;KAAe,KAAK;AACpB,OAAI;IACF,IAAM,IAAQ,EAAO,MAAM,EACrB,IAAM,EACT,aAAa,CACb,QAAQ,eAAe,IAAI,CAC3B,QAAQ,UAAU,GAAG;AACxB,QAAI,CAAC,GAAK;AACR,OACE,EACE,2CACA,6CACD,CACF;AACD;;IAMF,IAAM,KAHM,MAAM,EAAU,EAC1B,WAAW,EAAE,OAAO;KAAE;KAAK;KAAO,cAFpB,MAAM,GAAgB;KAEqB,EAAE,EAC5D,CAAC,EACkB,MAAM;AAC1B,QAAI,CAAC,EAAU,EAAQ,CAAE,OAAU,MAAM,mBAAmB;AAC5D,UAAM,EAAa,EAAQ;YACpB,GAAK;AACZ,MACE,aAAe,QACX,EAAI,UACJ,EAAG,wCAAwC,uBAAuB,CACvE;;;IAEF;EAAC;EAAa;EAAQ;EAAU;EAAgB;EAAW;EAAc;EAAG,CAAC,EAE1E,KAAe,EACnB,OAAO,MAAkB;AACvB,QAAM,EAAS,EAAM;IAEvB,CAAC,EAAS,CACX;AAED,KAAI,EAAM,QAAO;CAEjB,IAAM,IAAmB,GACvB,KAAe,EAAO,MAAM,IAAI,CAAC,KAAa,EAAc,WAAW;AAGzE,QACE,kBAAC,OAAD;EAAK,WAAW,EAAG,uCAAuC,GAAU;YAApE,CACG,EAAK,KAAK,MACT,kBAAC,GAAD;GAEE,IAAI,EAAI;GACR,OAAO,EAAI;GACX,OAAO,EAAI,SAAS,KAAA;GACpB,MAAM,EAAI,QAAQ,KAAA;GAClB,aAAa,EAAI,eAAe,KAAA;GAChC,WAAA;GACA,gBAAgB,KAAK,GAAa,EAAI,GAAG;GACzC,MAAK;GACL,EATK,EAAI,GAST,CACF,EAED,KACC,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CAOE,kBAAC,IAAD;IACE,MAAK;IACL,cAAY,EAAG,qCAAqC,UAAU;IAC9D,WAAW,EACT,gFACA,oDACA,qEACA,+FACD;cARH,CAUE,kBAAC,IAAD,EAAS,WAAU,UAAW,CAAA,EAC9B,kBAAC,QAAD,EAAA,UACG,EAAK,SAAS,IACX,EAAG,yBAAyB,MAAM,GAClC,EAAG,6BAA6B,WAAW,EAC1C,CAAA,CACQ;OAEjB,kBAAC,IAAD;IACE,WAAU;IACV,OAAM;IACN,MAAK;IACL,YAAY;cAEZ,kBAAC,IAAD;KAAS,cAAc;eAAvB;MACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,aAAa,EACX,uCACA,4BACD;QACD,OAAO;QACP,eAAe;QACf,WAAU;QACV,CAAA,EACD,KACC,kBAAC,GAAD,EAAS,WAAU,uFAAwF,CAAA,CAEzG;;MAEN,kBAAC,GAAD;OAAa,WAAU;iBAAvB;QACG,EAAO,MAAM,KAAK,MACjB,kBAAC,GAAD;SAAc,WAAU;mBACrB,EAAG,kCAAkC,iCAAiC;SAC1D,CAAA;QAGhB,EAAc,SAAS,KACtB,kBAAC,GAAD,EAAA,UACG,EAAc,KAAK,MAClB,kBAAC,GAAD;SAEE,OAAO,EAAI;SACX,gBAAgB,KAAK,EAAa,EAAI;SACtC,WAAU;SACV,UAAU;mBALZ;UAOG,EAAI,QAAQ,kBAAC,QAAD;WAAM,WAAU;qBAAW,EAAI;WAAY,CAAA;UACxD,kBAAC,QAAD;WACE,WAAU;WACV,OAAO,EAAI,QAAQ,EAAE,OAAO,EAAI,OAAO,GAAG,KAAA;qBAEzC,EAAI;WACA,CAAA;UACN,KAAU,kBAAC,GAAD,EAAS,WAAU,+BAAgC,CAAA;UAClD;WAdP,EAAI,GAcG,CACd,EACW,CAAA;QAGhB,KACC,kBAAC,GAAD,EAAA,UACE,kBAAC,GAAD;SACE,OAAO,aAAa;SACpB,gBAAgB,KAAK,IAAc;SACnC,WAAU;SACV,UAAU;mBAJZ,CAMG,IACC,kBAAC,GAAD,EAAS,WAAU,yBAA0B,CAAA,GAE7C,kBAAC,IAAD,EAAM,WAAU,YAAa,CAAA,EAE/B,kBAAC,QAAD;UAAM,WAAU;oBACb,IACG,EAAG,8BAA8B,cAAc,GAC/C,EAAG,4BAA4B,wBAAsB,EACnD,OAAO,GACR,CAAC;UACD,CAAA,CACK;YACD,CAAA;QAGhB,EAAO,MAAM,KAAK,MACjB,CAAC,KACD,EAAc,WAAW,KACzB,CAAC,KACC,kBAAC,GAAD;SAAc,WAAU;mBACrB,EAAG,iCAAiC,gBAAgB;SACxC,CAAA;QAGlB,KACC,kBAAC,OAAD;SAAK,WAAU;mBACZ;SACG,CAAA;QAEI;;MAEb,CAAC,KACA,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,IAAD,EAAG,WAAU,UAAW,CAAA,EACvB,EAAG,oCAAoC,oCAAoC,EAC1E,YACD,CAAC,CACE;;MAEA;;IACK,CAAA,CACT;KAER"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"permissions.js","names":[],"sources":["../../src/constants/permissions.ts"],"sourcesContent":["/**\n * Workspace MFE - Permission Constants\n *\n * Maps to action definitions in wspace-rbac-svc seed file.\n * Format: {resourceType}:{action}\n */\n\nexport const WORKSPACE_PERMISSIONS = {\n // Workspace\n WORKSPACE_LIST: 'workspace:list',\n WORKSPACE_READ: 'workspace:read',\n WORKSPACE_CREATE: 'workspace:create',\n WORKSPACE_UPDATE: 'workspace:update',\n WORKSPACE_DELETE: 'workspace:delete',\n WORKSPACE_ARCHIVE: 'workspace:archive',\n\n // Workspace Members\n MEMBER_LIST: 'workspace-member:list',\n MEMBER_READ: 'workspace-member:read',\n MEMBER_CREATE: 'workspace-member:create',\n MEMBER_UPDATE: 'workspace-member:update',\n MEMBER_DELETE: 'workspace-member:delete',\n\n // Projects\n PROJECT_LIST: 'project:list',\n PROJECT_READ: 'project:read',\n PROJECT_CREATE: 'project:create',\n PROJECT_UPDATE: 'project:update',\n PROJECT_DELETE: 'project:delete',\n PROJECT_ARCHIVE: 'project:archive',\n\n // Project Members\n PROJECT_MEMBER_LIST: 'project-member:list',\n PROJECT_MEMBER_READ: 'project-member:read',\n PROJECT_MEMBER_CREATE: 'project-member:create',\n PROJECT_MEMBER_DELETE: 'project-member:delete',\n\n // Settings\n SETTINGS_READ: 'settings:read',\n SETTINGS_WRITE: 'settings:write',\n\n // Activity / Audit\n ACTIVITY_READ: 'activity:read',\n} as const;\n"],"mappings":";AAOA,IAAa,IAAwB;CAEnC,gBAAgB;CAChB,gBAAgB;CAChB,kBAAkB;CAClB,kBAAkB;CAClB,kBAAkB;CAClB,mBAAmB;CAGnB,aAAa;CACb,aAAa;CACb,eAAe;CACf,eAAe;CACf,eAAe;CAGf,cAAc;CACd,cAAc;CACd,gBAAgB;CAChB,gBAAgB;CAChB,gBAAgB;CAChB,iBAAiB;CAGjB,qBAAqB;CACrB,qBAAqB;CACrB,uBAAuB;CACvB,uBAAuB;CAGvB,eAAe;CACf,gBAAgB;CAGhB,eAAe;CAChB"}