@burdenoff/microfe-workspaces 2026.731.1 → 2026.802.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.
- package/dist/WorkspacesRoutes.js +1 -1
- package/dist/WorkspacesRoutes.js.map +1 -1
- package/dist/components/ProjectCard.js +1 -1
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/WorkspaceCard.js +1 -1
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/components/WorkspaceContextPathSync.js +1 -1
- package/dist/components/WorkspaceContextPathSync.js.map +1 -1
- package/dist/hooks/useMemberMutations.js +0 -2
- package/dist/hooks/useMemberMutations.js.map +1 -1
- package/dist/hooks/useScopedNavigation.js +1 -1
- package/dist/hooks/useScopedNavigation.js.map +1 -1
- package/dist/hooks/useWorkspacesEventEmitter.js.map +1 -1
- package/dist/pages/ActivityListPage.js +1 -1
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/CreateWorkspacePage.js +1 -1
- package/dist/pages/CreateWorkspacePage.js.map +1 -1
- package/dist/pages/InviteAcceptancePage.js +1 -1
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +1 -1
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +1 -1
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +1 -1
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +1 -1
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +1 -1
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +1 -1
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspaceInvitePage.js +1 -1
- package/dist/pages/WorkspaceInvitePage.js.map +1 -1
- package/dist/pages/WorkspaceSettingsPage.js +1 -1
- package/dist/pages/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +1 -1
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/package.json +3 -3
package/dist/WorkspacesRoutes.js
CHANGED
|
@@ -4,7 +4,7 @@ import { WorkspacesListPage as n } from "./pages/WorkspacesListPage.js";
|
|
|
4
4
|
import { Suspense as r, lazy as i } from "react";
|
|
5
5
|
import { FeatureGate as a } from "@burdenoff/fe-libs/shared/feature-flags";
|
|
6
6
|
import { jsx as o, jsxs as s } from "react/jsx-runtime";
|
|
7
|
-
import { Navigate as c, Route as l, Routes as u } from "react-router
|
|
7
|
+
import { Navigate as c, Route as l, Routes as u } from "react-router";
|
|
8
8
|
//#region src/WorkspacesRoutes.tsx
|
|
9
9
|
var d = i(() => import("./pages/CreateWorkspacePage.js").then((e) => ({ default: e.CreateWorkspacePage }))), f = i(() => import("./pages/WorkspaceDetailPage.js").then((e) => ({ default: e.WorkspaceDetailPage }))), p = i(() => import("./pages/WorkspaceSettingsPage.js").then((e) => ({ default: e.WorkspaceSettingsPage }))), m = i(() => import("./pages/ProjectsListPage.js").then((e) => ({ default: e.ProjectsListPage }))), h = i(() => import("./pages/ProjectDetailPage.js").then((e) => ({ default: e.ProjectDetailPage }))), g = i(() => import("./pages/ProjectMembersPage.js").then((e) => ({ default: e.ProjectMembersPage }))), _ = i(() => import("./pages/MembersListPage.js").then((e) => ({ default: e.MembersListPage }))), v = i(() => import("./pages/ActivityListPage.js").then((e) => ({ default: e.ActivityListPage }))), y = i(() => import("./pages/InviteAcceptancePage.js").then((e) => ({ default: e.InviteAcceptancePage }))), b = i(() => import("./pages/PendingInvitesPage.js").then((e) => ({ default: e.PendingInvitesPage }))), x = i(() => import("./pages/WorkspaceInvitePage.js").then((e) => ({ default: e.WorkspaceInvitePage })));
|
|
10
10
|
function S() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspacesRoutes.js","names":[],"sources":["../src/WorkspacesRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Routes, Route, Navigate } from 'react-router
|
|
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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"}
|
|
@@ -10,7 +10,7 @@ import { useState as d } from "react";
|
|
|
10
10
|
import { Archive as f, Fingerprint as p, FolderKanban as m, MoreVertical as h, Pencil as g } from "lucide-react";
|
|
11
11
|
import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
12
12
|
import { Badge as b, Button as x, CardContent as S, CardHeader as C, CardTitle as w, DropdownMenu as T, DropdownMenuContent as E, DropdownMenuItem as D, DropdownMenuTrigger as O, EntityIdModal as k, GlassCard as A, useEntityIdModal as j } from "@burdenoff/fe-libs/ui";
|
|
13
|
-
import { useNavigate as M } from "react-router
|
|
13
|
+
import { useNavigate as M } from "react-router";
|
|
14
14
|
import { TagBadge as N } from "@burdenoff/fe-libs/tag";
|
|
15
15
|
//#region src/components/ProjectCard.tsx
|
|
16
16
|
function P({ project: P, selected: F = !1, onToggleSelect: I, onTagClick: L, canArchive: R = !1, canDelete: z = !1, canRename: B = !1 }) {
|
|
@@ -1 +1 @@
|
|
|
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-dom';\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';\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\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: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : 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={`Open project ${project.name}`}\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={`Select project ${project.name}`}\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\">Open project actions</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: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n 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 Rename\n </DropdownMenuItem>\n )}\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n 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 Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">Created {formatDate(project.createdAt)}</div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\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
|
+
{"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';\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\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: 'Delete project',\n message: `Are you sure you want to delete \"${project.name}\"?`,\n okButtonTitle: 'Delete',\n cancelButtonTitle: 'Cancel',\n });\n const ok =\n native === null ? confirm(`Are you sure you want to delete \"${project.name}\"?`) : 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={`Open project ${project.name}`}\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={`Select project ${project.name}`}\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\">Open project actions</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: 'Key', value: project.key },\n { label: 'Workspace ID', value: project.workspaceId },\n ],\n });\n }}\n >\n <Fingerprint size={16} className=\"mr-2\" />\n 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 Rename\n </DropdownMenuItem>\n )}\n {canArchive && (\n <DropdownMenuItem onClick={handleArchive}>\n <Archive size={16} className=\"mr-2\" />\n {project.archived ? 'Unarchive' : 'Archive'}\n </DropdownMenuItem>\n )}\n {canDelete && (\n <DropdownMenuItem onClick={handleDelete} className=\"text-status-error-text\">\n 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 Archived\n </Badge>\n )}\n </div>\n <div className=\"text-xs text-text-secondary\">Created {formatDate(project.createdAt)}</div>\n {projectTags.length > 0 && (\n <div className=\"flex flex-wrap gap-1.5\" aria-label=\"Project tags\">\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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"}
|
|
@@ -5,7 +5,7 @@ import { Building2 as r, FolderKanban as i, Users as a } from "lucide-react";
|
|
|
5
5
|
import { useActiveContext as o } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
6
6
|
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
7
7
|
import { Badge as l, CardContent as u, CardHeader as d, CardTitle as f, GlassCard as p } from "@burdenoff/fe-libs/ui";
|
|
8
|
-
import { useNavigate as m } from "react-router
|
|
8
|
+
import { useNavigate as m } from "react-router";
|
|
9
9
|
//#region src/components/WorkspaceCard.tsx
|
|
10
10
|
function h({ workspace: h }) {
|
|
11
11
|
let g = m(), { basePath: _ = "/" } = e(), { withCurrentSearch: v } = n(), { setOrganization: y, setWorkspace: b, setProject: x, setTenant: S } = o(), C = () => {
|
|
@@ -1 +1 @@
|
|
|
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
|
|
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';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n}\n\nexport function WorkspaceCard({ workspace }: WorkspaceCardProps) {\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={`Open workspace ${workspace.name}`}\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 {workspace.memberCount} member{workspace.memberCount === 1 ? '' : 's'}\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 {workspace.projectCount} project{workspace.projectCount === 1 ? '' : 's'}\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":";;;;;;;;;AAaA,SAAgB,EAAc,EAAE,gBAAiC;CAC/D,IAAM,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,kBAAkB,EAAU;EACxC,aAAU;YARZ,CAUE,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,UAAA;KACG,EAAU;KAAY;KAAQ,EAAU,gBAAgB,IAAI,KAAK;KAC7D,EAAA,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,UAAA;KACG,EAAU;KAAa;KAAS,EAAU,iBAAiB,IAAI,KAAK;KAChE,EAAA,CAAA,CACH;MAEJ;MACL,EAAU,UACT,kBAAC,KAAD;GAAG,WAAU;aAAyC,EAAU;GAAW,CAAA,CAEjE,EAAA,CAAA,CACJ"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useWorkspacesContext as e } from "../providers/WorkspacesProvider.js";
|
|
2
2
|
import { useEffect as t, useRef as n } from "react";
|
|
3
|
-
import { useNavigate as r } from "react-router
|
|
3
|
+
import { useNavigate as r } from "react-router";
|
|
4
4
|
//#region src/components/WorkspaceContextPathSync.tsx
|
|
5
5
|
var i = new Set([
|
|
6
6
|
"list",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceContextPathSync.js","names":[],"sources":["../../src/components/WorkspaceContextPathSync.tsx"],"sourcesContent":["import { useEffect, useRef } from 'react';\nimport { useNavigate } from 'react-router
|
|
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"}
|
|
@@ -128,14 +128,12 @@ var b = () => {
|
|
|
128
128
|
y(i, e.id, t), o("workspace-invitation.created", {
|
|
129
129
|
invitationId: e.id,
|
|
130
130
|
workspaceId: e.workspaceId,
|
|
131
|
-
email: e.email,
|
|
132
131
|
source: "microfe-workspaces"
|
|
133
132
|
});
|
|
134
133
|
},
|
|
135
134
|
onError: (e, t) => {
|
|
136
135
|
o("workspace-invitation.create_failed", {
|
|
137
136
|
workspaceId: t?.workspaceId || s || void 0,
|
|
138
|
-
email: t?.email,
|
|
139
137
|
source: "microfe-workspaces",
|
|
140
138
|
errorMessage: p(e)
|
|
141
139
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useMemberMutations.js","names":[],"sources":["../../src/hooks/useMemberMutations.ts"],"sourcesContent":["import { useMutation, useQueryClient, type QueryClient } from '@tanstack/react-query';\nimport { print } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport {\n AddWorkspaceMemberDocument,\n RemoveWorkspaceMemberDocument,\n RemoveUserFromWorkspaceDocument,\n CreateInvitationDocument,\n UpdateInvitationDocument,\n AcceptInvitationDocument,\n RejectInvitationDocument,\n CancelInvitationDocument,\n ResendInvitationDocument,\n DeleteInvitationDocument,\n BulkCreateInvitationsDocument,\n} from '../generated/wspace-operations';\nimport type {\n WorkspaceMember,\n WorkspaceInvitation,\n AddWorkspaceMemberInput,\n CreateInvitationInput,\n UpdateInvitationInput,\n} from '../types';\nimport { useWorkspacesEventEmitter } from './useWorkspacesEventEmitter';\nimport { safeTelemetryError } from '../utils/telemetryError';\n\nfunction invalidateMemberViews(queryClient: QueryClient, workspaceId?: string | null) {\n queryClient.invalidateQueries({ queryKey: ['workspaceMembers'] });\n queryClient.invalidateQueries({ queryKey: ['workspaceMembersInfinite'] });\n queryClient.invalidateQueries({ queryKey: ['userWorkspaces'] });\n queryClient.invalidateQueries({ queryKey: ['myWorkspaces'] });\n queryClient.invalidateQueries({ queryKey: ['dashboard', 'global'] });\n if (workspaceId) {\n queryClient.invalidateQueries({ queryKey: ['workspaceMembers', workspaceId] });\n queryClient.invalidateQueries({ queryKey: ['workspaceMembersInfinite', workspaceId] });\n queryClient.invalidateQueries({ queryKey: ['dashboard', 'workspace', workspaceId] });\n }\n}\n\nfunction invalidateInvitationViews(\n queryClient: QueryClient,\n invitationId?: string,\n workspaceId?: string | null,\n // Only TERMINAL actions (accept / reject / cancel / delete) leave the invite\n // no longer pending and should evict it from the recipient's My Invites list.\n // Non-terminal actions (create / update / resend) keep it PENDING and must NOT\n // remove it — otherwise resending/updating a still-pending invite would make\n // it vanish from the recipient's list until a full reload.\n evictFromPending = false\n) {\n // Optimistically drop the invitation from the recipient's pending list so the\n // card disappears immediately after a terminal action. Invalidation alone has\n // proven unreliable at triggering a refetch of `myPendingInvitations` here —\n // the accepted invite stays on screen with no feedback, which leads users to\n // re-click Accept and hit \"Invitation is already accepted\". The cache write\n // is the source of the visible update; the invalidations below still run for\n // eventual consistency. Matches all `['myPendingInvitations', <email>]` keys.\n if (invitationId && evictFromPending) {\n queryClient.setQueriesData<WorkspaceInvitation[] | undefined>(\n { queryKey: ['myPendingInvitations'] },\n (old) => (Array.isArray(old) ? old.filter((invite) => invite.id !== invitationId) : old)\n );\n }\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations'] });\n queryClient.invalidateQueries({ queryKey: ['myPendingInvitations'] });\n if (invitationId) {\n queryClient.invalidateQueries({ queryKey: ['invitation', invitationId] });\n }\n if (workspaceId) {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations', workspaceId] });\n }\n}\n\n/**\n * Hook to add a member to a workspace\n * Requires x-workspace-id header for workspace context\n */\nexport const useAddWorkspaceMember = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (input: AddWorkspaceMemberInput): Promise<WorkspaceMember> => {\n const result = await graphqlFetch<{ addWorkspaceMember: WorkspaceMember }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(AddWorkspaceMemberDocument),\n variables: { input },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.addWorkspaceMember;\n },\n onSuccess: (data) => {\n invalidateMemberViews(queryClient, data.workspaceId);\n emit('workspace-member.added', {\n memberId: data.id,\n workspaceId: data.workspaceId,\n userId: data.userId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, input) => {\n emit('workspace-member.add_failed', {\n workspaceId: workspaceId || undefined,\n userId: input?.userId,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to remove a member from a workspace by member ID\n */\nexport const useRemoveWorkspaceMember = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<boolean> => {\n const result = await graphqlFetch<{ removeWorkspaceMember: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RemoveWorkspaceMemberDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.removeWorkspaceMember;\n },\n onSuccess: (_, id) => {\n invalidateMemberViews(queryClient, effectiveWorkspaceId);\n emit('workspace-member.removed', { memberId: id, source: 'microfe-workspaces' });\n },\n onError: (err, id) => {\n emit('workspace-member.remove_failed', {\n memberId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to remove a user from a workspace\n * Requires x-workspace-id header for workspace context\n */\nexport const useRemoveUserFromWorkspace = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (userId: string): Promise<boolean> => {\n const result = await graphqlFetch<{ removeUserFromWorkspace: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RemoveUserFromWorkspaceDocument),\n variables: { userId },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.removeUserFromWorkspace;\n },\n onSuccess: (_, userId) => {\n invalidateMemberViews(queryClient, effectiveWorkspaceId);\n queryClient.invalidateQueries({ queryKey: ['userWorkspaces', userId] });\n emit('workspace-member.removed', { userId, source: 'microfe-workspaces' });\n },\n onError: (err, userId) => {\n emit('workspace-member.remove_failed', {\n userId,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * NOTE: The updateWorkspaceMemberRoles mutation has been removed from the backend.\n * Role management is now handled via invitations - when creating an invitation,\n * you can specify roleIds that will be assigned when the invitation is accepted.\n *\n * Use CreateInvitationInput.roleIds to assign roles to new members.\n */\n\n/**\n * Hook to create a workspace invitation\n * Uses x-workspace-id header as fallback when input.workspaceId is not provided\n */\nexport const useCreateInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (input: CreateInvitationInput): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ createInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(CreateInvitationDocument),\n variables: { input },\n workspaceId: input.workspaceId || effectiveWorkspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header fallback\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.createInvitation;\n },\n onSuccess: (data) => {\n const wsId = data.workspaceId ?? effectiveWorkspaceId;\n invalidateInvitationViews(queryClient, data.id, wsId);\n emit('workspace-invitation.created', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n email: data.email,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, input) => {\n emit('workspace-invitation.create_failed', {\n workspaceId: input?.workspaceId || effectiveWorkspaceId || undefined,\n email: input?.email,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to update an invitation\n */\nexport const useUpdateInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async ({\n id,\n input,\n }: {\n id: string;\n input: UpdateInvitationInput;\n }): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ updateInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(UpdateInvitationDocument),\n variables: { id, input },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.updateInvitation;\n },\n onSuccess: (data) => {\n invalidateInvitationViews(queryClient, data.id, data.workspaceId);\n emit('workspace-invitation.updated', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, variables) => {\n emit('workspace-invitation.update_failed', {\n invitationId: variables?.id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to accept an invitation\n */\nexport const useAcceptInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ acceptInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(AcceptInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.acceptInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is now accepted — evict it from My Invites.\n invalidateInvitationViews(queryClient, data.id, data.workspaceId, true);\n invalidateMemberViews(queryClient, data.workspaceId);\n emit('workspace-invitation.accepted', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.accept_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to reject an invitation\n */\nexport const useRejectInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ rejectInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RejectInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.rejectInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is now rejected — evict it from My Invites.\n invalidateInvitationViews(queryClient, data.id, data.workspaceId, true);\n emit('workspace-invitation.rejected', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.reject_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to cancel an invitation\n */\nexport const useCancelInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ cancelInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(CancelInvitationDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.cancelInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is cancelled — evict it from My Invites.\n invalidateInvitationViews(\n queryClient,\n data.id,\n data.workspaceId ?? effectiveWorkspaceId,\n true\n );\n emit('workspace-invitation.cancelled', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.cancel_failed', {\n invitationId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to resend an invitation\n */\nexport const useResendInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ resendInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(ResendInvitationDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.resendInvitation;\n },\n onSuccess: (data) => {\n invalidateInvitationViews(queryClient, data.id, data.workspaceId ?? effectiveWorkspaceId);\n emit('workspace-invitation.resent', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.resend_failed', {\n invitationId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to delete an invitation\n */\nexport const useDeleteInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<boolean> => {\n const result = await graphqlFetch<{ deleteInvitation: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(DeleteInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.deleteInvitation;\n },\n onSuccess: (_, id) => {\n // Terminal: the invite is deleted — evict it from My Invites.\n invalidateInvitationViews(queryClient, id, workspaceId, true);\n emit('workspace-invitation.deleted', { invitationId: id, source: 'microfe-workspaces' });\n },\n onError: (err, id) => {\n emit('workspace-invitation.delete_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to bulk create invitations\n * Uses x-workspace-id header as fallback when inputs[].workspaceId is not provided\n */\nexport const useBulkCreateInvitations = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (inputs: CreateInvitationInput[]): Promise<WorkspaceInvitation[]> => {\n const result = await graphqlFetch<{ bulkCreateInvitations: WorkspaceInvitation[] }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(BulkCreateInvitationsDocument),\n variables: { inputs },\n workspaceId: inputs[0]?.workspaceId || workspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header fallback\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.bulkCreateInvitations;\n },\n onSuccess: (invitations) => {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations'] });\n queryClient.invalidateQueries({ queryKey: ['myPendingInvitations'] });\n const workspaceIds = new Set(\n invitations.map((invitation) => invitation.workspaceId).filter(Boolean)\n );\n for (const wsId of workspaceIds) {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations', wsId] });\n }\n },\n });\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useMemberMutations.js","names":[],"sources":["../../src/hooks/useMemberMutations.ts"],"sourcesContent":["import { useMutation, useQueryClient, type QueryClient } from '@tanstack/react-query';\nimport { print } from 'graphql';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport {\n AddWorkspaceMemberDocument,\n RemoveWorkspaceMemberDocument,\n RemoveUserFromWorkspaceDocument,\n CreateInvitationDocument,\n UpdateInvitationDocument,\n AcceptInvitationDocument,\n RejectInvitationDocument,\n CancelInvitationDocument,\n ResendInvitationDocument,\n DeleteInvitationDocument,\n BulkCreateInvitationsDocument,\n} from '../generated/wspace-operations';\nimport type {\n WorkspaceMember,\n WorkspaceInvitation,\n AddWorkspaceMemberInput,\n CreateInvitationInput,\n UpdateInvitationInput,\n} from '../types';\nimport { useWorkspacesEventEmitter } from './useWorkspacesEventEmitter';\nimport { safeTelemetryError } from '../utils/telemetryError';\n\nfunction invalidateMemberViews(queryClient: QueryClient, workspaceId?: string | null) {\n queryClient.invalidateQueries({ queryKey: ['workspaceMembers'] });\n queryClient.invalidateQueries({ queryKey: ['workspaceMembersInfinite'] });\n queryClient.invalidateQueries({ queryKey: ['userWorkspaces'] });\n queryClient.invalidateQueries({ queryKey: ['myWorkspaces'] });\n queryClient.invalidateQueries({ queryKey: ['dashboard', 'global'] });\n if (workspaceId) {\n queryClient.invalidateQueries({ queryKey: ['workspaceMembers', workspaceId] });\n queryClient.invalidateQueries({ queryKey: ['workspaceMembersInfinite', workspaceId] });\n queryClient.invalidateQueries({ queryKey: ['dashboard', 'workspace', workspaceId] });\n }\n}\n\nfunction invalidateInvitationViews(\n queryClient: QueryClient,\n invitationId?: string,\n workspaceId?: string | null,\n // Only TERMINAL actions (accept / reject / cancel / delete) leave the invite\n // no longer pending and should evict it from the recipient's My Invites list.\n // Non-terminal actions (create / update / resend) keep it PENDING and must NOT\n // remove it — otherwise resending/updating a still-pending invite would make\n // it vanish from the recipient's list until a full reload.\n evictFromPending = false\n) {\n // Optimistically drop the invitation from the recipient's pending list so the\n // card disappears immediately after a terminal action. Invalidation alone has\n // proven unreliable at triggering a refetch of `myPendingInvitations` here —\n // the accepted invite stays on screen with no feedback, which leads users to\n // re-click Accept and hit \"Invitation is already accepted\". The cache write\n // is the source of the visible update; the invalidations below still run for\n // eventual consistency. Matches all `['myPendingInvitations', <email>]` keys.\n if (invitationId && evictFromPending) {\n queryClient.setQueriesData<WorkspaceInvitation[] | undefined>(\n { queryKey: ['myPendingInvitations'] },\n (old) => (Array.isArray(old) ? old.filter((invite) => invite.id !== invitationId) : old)\n );\n }\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations'] });\n queryClient.invalidateQueries({ queryKey: ['myPendingInvitations'] });\n if (invitationId) {\n queryClient.invalidateQueries({ queryKey: ['invitation', invitationId] });\n }\n if (workspaceId) {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations', workspaceId] });\n }\n}\n\n/**\n * Hook to add a member to a workspace\n * Requires x-workspace-id header for workspace context\n */\nexport const useAddWorkspaceMember = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (input: AddWorkspaceMemberInput): Promise<WorkspaceMember> => {\n const result = await graphqlFetch<{ addWorkspaceMember: WorkspaceMember }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(AddWorkspaceMemberDocument),\n variables: { input },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.addWorkspaceMember;\n },\n onSuccess: (data) => {\n invalidateMemberViews(queryClient, data.workspaceId);\n emit('workspace-member.added', {\n memberId: data.id,\n workspaceId: data.workspaceId,\n userId: data.userId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, input) => {\n emit('workspace-member.add_failed', {\n workspaceId: workspaceId || undefined,\n userId: input?.userId,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to remove a member from a workspace by member ID\n */\nexport const useRemoveWorkspaceMember = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<boolean> => {\n const result = await graphqlFetch<{ removeWorkspaceMember: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RemoveWorkspaceMemberDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.removeWorkspaceMember;\n },\n onSuccess: (_, id) => {\n invalidateMemberViews(queryClient, effectiveWorkspaceId);\n emit('workspace-member.removed', { memberId: id, source: 'microfe-workspaces' });\n },\n onError: (err, id) => {\n emit('workspace-member.remove_failed', {\n memberId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to remove a user from a workspace\n * Requires x-workspace-id header for workspace context\n */\nexport const useRemoveUserFromWorkspace = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (userId: string): Promise<boolean> => {\n const result = await graphqlFetch<{ removeUserFromWorkspace: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RemoveUserFromWorkspaceDocument),\n variables: { userId },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.removeUserFromWorkspace;\n },\n onSuccess: (_, userId) => {\n invalidateMemberViews(queryClient, effectiveWorkspaceId);\n queryClient.invalidateQueries({ queryKey: ['userWorkspaces', userId] });\n emit('workspace-member.removed', { userId, source: 'microfe-workspaces' });\n },\n onError: (err, userId) => {\n emit('workspace-member.remove_failed', {\n userId,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * NOTE: The updateWorkspaceMemberRoles mutation has been removed from the backend.\n * Role management is now handled via invitations - when creating an invitation,\n * you can specify roleIds that will be assigned when the invitation is accepted.\n *\n * Use CreateInvitationInput.roleIds to assign roles to new members.\n */\n\n/**\n * Hook to create a workspace invitation\n * Uses x-workspace-id header as fallback when input.workspaceId is not provided\n */\nexport const useCreateInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (input: CreateInvitationInput): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ createInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(CreateInvitationDocument),\n variables: { input },\n workspaceId: input.workspaceId || effectiveWorkspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header fallback\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.createInvitation;\n },\n onSuccess: (data) => {\n const wsId = data.workspaceId ?? effectiveWorkspaceId;\n invalidateInvitationViews(queryClient, data.id, wsId);\n emit('workspace-invitation.created', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, input) => {\n emit('workspace-invitation.create_failed', {\n workspaceId: input?.workspaceId || effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to update an invitation\n */\nexport const useUpdateInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async ({\n id,\n input,\n }: {\n id: string;\n input: UpdateInvitationInput;\n }): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ updateInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(UpdateInvitationDocument),\n variables: { id, input },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.updateInvitation;\n },\n onSuccess: (data) => {\n invalidateInvitationViews(queryClient, data.id, data.workspaceId);\n emit('workspace-invitation.updated', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, variables) => {\n emit('workspace-invitation.update_failed', {\n invitationId: variables?.id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to accept an invitation\n */\nexport const useAcceptInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ acceptInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(AcceptInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.acceptInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is now accepted — evict it from My Invites.\n invalidateInvitationViews(queryClient, data.id, data.workspaceId, true);\n invalidateMemberViews(queryClient, data.workspaceId);\n emit('workspace-invitation.accepted', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.accept_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to reject an invitation\n */\nexport const useRejectInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ rejectInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(RejectInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.rejectInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is now rejected — evict it from My Invites.\n invalidateInvitationViews(queryClient, data.id, data.workspaceId, true);\n emit('workspace-invitation.rejected', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.reject_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to cancel an invitation\n */\nexport const useCancelInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ cancelInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(CancelInvitationDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.cancelInvitation;\n },\n onSuccess: (data) => {\n // Terminal: the invite is cancelled — evict it from My Invites.\n invalidateInvitationViews(\n queryClient,\n data.id,\n data.workspaceId ?? effectiveWorkspaceId,\n true\n );\n emit('workspace-invitation.cancelled', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.cancel_failed', {\n invitationId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to resend an invitation\n */\nexport const useResendInvitation = (workspaceIdOverride?: string) => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n const effectiveWorkspaceId = workspaceIdOverride || workspaceId;\n\n return useMutation({\n mutationFn: async (id: string): Promise<WorkspaceInvitation> => {\n const result = await graphqlFetch<{ resendInvitation: WorkspaceInvitation }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(ResendInvitationDocument),\n variables: { id },\n workspaceId: effectiveWorkspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.resendInvitation;\n },\n onSuccess: (data) => {\n invalidateInvitationViews(queryClient, data.id, data.workspaceId ?? effectiveWorkspaceId);\n emit('workspace-invitation.resent', {\n invitationId: data.id,\n workspaceId: data.workspaceId,\n source: 'microfe-workspaces',\n });\n },\n onError: (err, id) => {\n emit('workspace-invitation.resend_failed', {\n invitationId: id,\n workspaceId: effectiveWorkspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to delete an invitation\n */\nexport const useDeleteInvitation = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n const { emit } = useWorkspacesEventEmitter();\n\n return useMutation({\n mutationFn: async (id: string): Promise<boolean> => {\n const result = await graphqlFetch<{ deleteInvitation: boolean }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(DeleteInvitationDocument),\n variables: { id },\n workspaceId: workspaceId || undefined,\n workspaceToken: true, // Required by gateway RBAC for wspace-scoped operations\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.deleteInvitation;\n },\n onSuccess: (_, id) => {\n // Terminal: the invite is deleted — evict it from My Invites.\n invalidateInvitationViews(queryClient, id, workspaceId, true);\n emit('workspace-invitation.deleted', { invitationId: id, source: 'microfe-workspaces' });\n },\n onError: (err, id) => {\n emit('workspace-invitation.delete_failed', {\n invitationId: id,\n workspaceId: workspaceId || undefined,\n source: 'microfe-workspaces',\n errorMessage: safeTelemetryError(err),\n });\n },\n });\n};\n\n/**\n * Hook to bulk create invitations\n * Uses x-workspace-id header as fallback when inputs[].workspaceId is not provided\n */\nexport const useBulkCreateInvitations = () => {\n const { authToken, workspaceId } = useWorkspacesContext();\n const queryClient = useQueryClient();\n\n return useMutation({\n mutationFn: async (inputs: CreateInvitationInput[]): Promise<WorkspaceInvitation[]> => {\n const result = await graphqlFetch<{ bulkCreateInvitations: WorkspaceInvitation[] }>({\n gateway: 'workspace',\n authToken: authToken || undefined,\n query: print(BulkCreateInvitationsDocument),\n variables: { inputs },\n workspaceId: inputs[0]?.workspaceId || workspaceId || undefined,\n workspaceToken: true, // Auto-read from profile context for x-workspace-id header fallback\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message || 'GraphQL error');\n }\n\n return result.data!.bulkCreateInvitations;\n },\n onSuccess: (invitations) => {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations'] });\n queryClient.invalidateQueries({ queryKey: ['myPendingInvitations'] });\n const workspaceIds = new Set(\n invitations.map((invitation) => invitation.workspaceId).filter(Boolean)\n );\n for (const wsId of workspaceIds) {\n queryClient.invalidateQueries({ queryKey: ['workspaceInvitations', wsId] });\n }\n },\n });\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useScopedNavigation.js","names":[],"sources":["../../src/hooks/useScopedNavigation.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useLocation } from 'react-router
|
|
1
|
+
{"version":3,"file":"useScopedNavigation.js","names":[],"sources":["../../src/hooks/useScopedNavigation.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useLocation } from 'react-router';\n\nexport function useScopedNavigation() {\n const location = useLocation();\n\n const withCurrentSearch = useCallback(\n (path: string) => `${path}${location.search || ''}`,\n [location.search]\n );\n\n return { withCurrentSearch };\n}\n"],"mappings":";;;AAGA,SAAgB,IAAsB;CACpC,IAAM,IAAW,GAAa;AAO9B,QAAO,EAAE,mBALiB,GACvB,MAAiB,GAAG,IAAO,EAAS,UAAU,MAC/C,CAAC,EAAS,OAAO,CAClB,EAE2B"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWorkspacesEventEmitter.js","names":[],"sources":["../../src/hooks/useWorkspacesEventEmitter.ts"],"sourcesContent":["/**\n * Workspaces-domain event emitter. Emits lifecycle events to the host shell's\n * event bus per `~/products/infra/infra-specs/microfrontends/47-FRONTEND-EVENT-BUS.md`.\n *\n * The MFE never sinks directly to Rybbit/audit — it goes through the bus.\n *\n * The fe-libs `EventBus.emit` is statically typed against `BurdenoffEventName
|
|
1
|
+
{"version":3,"file":"useWorkspacesEventEmitter.js","names":[],"sources":["../../src/hooks/useWorkspacesEventEmitter.ts"],"sourcesContent":["/**\n * Workspaces-domain event emitter. Emits lifecycle events to the host shell's\n * event bus per `~/products/infra/infra-specs/microfrontends/47-FRONTEND-EVENT-BUS.md`.\n *\n * The MFE never sinks directly to Rybbit/audit — it goes through the bus.\n *\n * The installed `@burdenoff/fe-libs` `EventBus.emit` is statically typed\n * against its published `BurdenoffEventName` union, which doesn't yet include\n * the workspaces-domain names added in the local fe-libs source. Until those\n * types are published, the runtime bus is invoked via a narrow dynamic cast.\n * The cast is sound because the underlying bus accepts any string name.\n */\nimport { useCallback } from 'react';\n\nimport { useEventBus } from '@burdenoff/fe-libs/shared/events';\n\nexport type WorkspacesDomainEvent =\n // Lifecycle (success)\n | 'workspace.created'\n | 'workspace.updated'\n | 'workspace.deleted'\n | 'workspace.archived'\n | 'workspace.reactivated'\n | 'project.created'\n | 'project.updated'\n | 'project.deleted'\n | 'project.archived'\n | 'project.unarchived'\n | 'project-member.added'\n | 'project-member.removed'\n | 'workspace-member.added'\n | 'workspace-member.removed'\n | 'workspace-member.role-updated'\n | 'workspace-invitation.created'\n | 'workspace-invitation.updated'\n | 'workspace-invitation.accepted'\n | 'workspace-invitation.rejected'\n | 'workspace-invitation.cancelled'\n | 'workspace-invitation.resent'\n | 'workspace-invitation.deleted'\n | 'workspace-activity.appended'\n // Failure / observability — forwarded to OtelBrowserSink + audit sink\n | 'workspace.create_failed'\n | 'workspace.update_failed'\n | 'workspace.delete_failed'\n | 'workspace.archive_failed'\n | 'workspace.reactivate_failed'\n | 'project.create_failed'\n | 'project.update_failed'\n | 'project.delete_failed'\n | 'project.archive_failed'\n | 'project.unarchive_failed'\n | 'project-member.add_failed'\n | 'project-member.remove_failed'\n | 'workspace-member.add_failed'\n | 'workspace-member.remove_failed'\n | 'workspace-member.role-update_failed'\n | 'workspace-invitation.create_failed'\n | 'workspace-invitation.update_failed'\n | 'workspace-invitation.accept_failed'\n | 'workspace-invitation.reject_failed'\n | 'workspace-invitation.cancel_failed'\n | 'workspace-invitation.resend_failed'\n | 'workspace-invitation.delete_failed'\n // Module-scoped (plural) — describes a navigation event into the module,\n // not an action on a single resource.\n | 'workspaces.page.viewed';\n\nexport interface WorkspacesEventPayload {\n workspaceId?: string;\n projectId?: string;\n memberId?: string;\n invitationId?: string;\n userId?: string;\n source?: string;\n // Observability fields (no PII per SRE guide §7)\n page?: string;\n durationMs?: number;\n errorCode?: string;\n errorMessage?: string;\n}\n\nexport function useWorkspacesEventEmitter(): {\n emit: (event: WorkspacesDomainEvent, payload?: WorkspacesEventPayload) => void;\n} {\n const bus = useEventBus();\n // Memoize the emit callback so consumers (notably `useWorkspacesPageView`)\n // can put it in effect dep arrays without firing on every render.\n const emit = useCallback(\n (event: WorkspacesDomainEvent, payload: WorkspacesEventPayload = {}) => {\n try {\n // The installed `@burdenoff/fe-libs` `EventBus.emit` is typed against\n // the published BurdenoffEventName union. Workspaces-domain names\n // (workspace.created, project.created, workspaces.page.viewed, etc.)\n // were added to the local fe-libs source and will be typed once\n // fe-libs is republished. Until then, use a narrow runtime cast so\n // bus failures never break workspace operations.\n type DynamicEmit = (e: string, p: unknown) => void;\n (bus as unknown as { emit: DynamicEmit }).emit(event, payload);\n } catch (err) {\n // Bus failures must not break workspace operations — but in dev we\n // surface the error so a wrong event shape doesn't disappear silently.\n if (import.meta.env?.DEV) {\n console.warn('[useWorkspacesEventEmitter] event bus emit failed', {\n event,\n payload,\n err,\n });\n }\n }\n },\n [bus]\n );\n return { emit };\n}\n"],"mappings":";;;AAkFA,SAAgB,IAEd;CACA,IAAM,IAAM,GAAa;AA4BzB,QAAO,EAAE,MAzBI,GACV,GAA8B,IAAkC,EAAE,KAAK;AACtE,MAAI;AAQD,KAAyC,KAAK,GAAO,EAAQ;UAClD;IAYhB,CAAC,EAAI,CACN,EACc"}
|
|
@@ -5,7 +5,7 @@ import { formatDate as r } from "../utils/formatters.js";
|
|
|
5
5
|
import { useWorkspaceActivity as i } from "../hooks/useWorkspaceActivity.js";
|
|
6
6
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
7
7
|
import { CardContent as s, CardHeader as c, CardTitle as l, GlassCard as u, IllustratedEmptyState as d, PagePurpose as f, Skeleton as p } from "@burdenoff/fe-libs/ui";
|
|
8
|
-
import { useParams as m } from "react-router
|
|
8
|
+
import { useParams as m } from "react-router";
|
|
9
9
|
import { useI18n as h } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
10
10
|
//#region src/pages/ActivityListPage.tsx
|
|
11
11
|
function g() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActivityListPage.js","names":[],"sources":["../../src/pages/ActivityListPage.tsx"],"sourcesContent":["import { useParams } from 'react-router
|
|
1
|
+
{"version":3,"file":"ActivityListPage.js","names":[],"sources":["../../src/pages/ActivityListPage.tsx"],"sourcesContent":["import { useParams } from 'react-router';\nimport {\n GlassCard,\n CardContent,\n CardHeader,\n CardTitle,\n Skeleton,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { formatDate } from '../utils/formatters';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\n\nexport function ActivityListPage() {\n const { t } = useI18n();\n\n // The URL path `:workspaceId` (when present) intentionally overrides the active\n // context. WorkspaceContextPathSync rewrites this segment on a context switch so\n // the override can't point at a stale workspace. (BOFF-2805 follow-up)\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.activity', { workspaceId, requireParams: true });\n const { data: activities, isLoading, error } = useWorkspaceActivity(workspaceId);\n const { canViewActivity, isLoading: permissionsLoading } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <Skeleton className=\"h-64\" />\n </div>\n );\n }\n\n if (!canViewActivity) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-2\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'activityList.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'activityList.accessDeniedDescription',\n \"You don't have permission to view activity.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'activityList.title', 'Activity')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'activityList.description', 'Track workspace activity')}\n </p>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'activityList.purpose',\n 'This is the audit trail for the workspace — a running log of who did what and when. Use it to catch up after time away, confirm a change went through, or investigate when and by whom a project or member was modified.'\n )}\n </PagePurpose>\n\n {isLoading ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardContent className=\"py-8 text-sm text-text-muted\">\n {tWithFallback(t, 'activityList.loading', 'Loading activity…')}\n </CardContent>\n </GlassCard>\n ) : error ? (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardContent className=\"py-8 text-sm text-status-error-text\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'activityList.loadError', 'Failed to load activity')}\n </CardContent>\n </GlassCard>\n ) : (activities?.length ?? 0) === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title={tWithFallback(t, 'activityList.emptyTitle', 'No recent activity')}\n description={tWithFallback(\n t,\n 'activityList.emptyDescription',\n 'Recent workspace actions will appear here as your team creates, updates, and manages work.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {activities?.map((item) => (\n <GlassCard key={item.id} treatment=\"insight\" className=\"border-border-seam\">\n <CardHeader className=\"pb-2\">\n <CardTitle className=\"text-base\">{item.description || item.action}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-1 text-sm text-text-muted\">\n <p>\n {tWithFallback(t, 'activityList.labelAction', 'Action')}:{' '}\n <span className=\"text-text-primary\">{item.action}</span>\n </p>\n {item.resourceType && (\n <p>\n {tWithFallback(t, 'activityList.labelResource', 'Resource')}:{' '}\n <span className=\"text-text-primary\">{item.resourceType}</span>\n </p>\n )}\n <p>\n {tWithFallback(t, 'activityList.labelWhen', 'When')}:{' '}\n <span className=\"text-text-primary\">{formatDate(item.timestamp)}</span>\n </p>\n <p>\n {tWithFallback(t, 'activityList.labelActor', 'Actor')}:{' '}\n <span className=\"break-all text-text-primary\">{item.userId}</span>\n </p>\n </CardContent>\n </GlassCard>\n ))}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;AAiBA,SAAgB,IAAmB;CACjC,IAAM,EAAE,SAAM,GAAS,EAKjB,EAAE,mBAAgB,GAAoC;AAC5D,GAAsB,uBAAuB;EAAE;EAAa,eAAe;EAAM,CAAC;CAClF,IAAM,EAAE,MAAM,GAAY,cAAW,aAAU,EAAqB,EAAY,EAC1E,EAAE,oBAAiB,WAAW,MAAuB,GAAyB;AA8BpF,QA5BI,IAEA,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,EACnC,kBAAC,GAAD,EAAU,WAAU,QAAS,CAAA,CACzB;MAIL,IAoBH,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAc,GAAG,sBAAsB,WAAW;IAChD,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAG,4BAA4B,2BAA2B;IACvE,CAAA,CACA,EAAA,CAAA;GAEN,kBAAC,GAAD,EAAA,UACG,EACC,GACA,wBACA,2NACD,EACW,CAAA;GAEb,IACC,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,GAAD;KAAa,WAAU;eACpB,EAAc,GAAG,wBAAwB,oBAAoB;KAClD,CAAA;IACJ,CAAA,GACV,IACF,kBAAC,GAAD;IAAW,WAAU;IAAQ,WAAU;cACrC,kBAAC,GAAD;KAAa,WAAU;eACpB,aAAiB,QACd,EAAM,UACN,EAAc,GAAG,0BAA0B,0BAA0B;KAC7D,CAAA;IACJ,CAAA,IACT,GAAY,UAAU,OAAO,IAChC,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAc,GAAG,2BAA2B,qBAAqB;IACxE,aAAa,EACX,GACA,iCACA,6FACD;IACD,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACZ,GAAY,KAAK,MAChB,kBAAC,GAAD;KAAyB,WAAU;KAAU,WAAU;eAAvD,CACE,kBAAC,GAAD;MAAY,WAAU;gBACpB,kBAAC,GAAD;OAAW,WAAU;iBAAa,EAAK,eAAe,EAAK;OAAmB,CAAA;MACnE,CAAA,EACb,kBAAC,GAAD;MAAa,WAAU;gBAAvB;OACE,kBAAC,KAAD,EAAA,UAAA;QACG,EAAc,GAAG,4BAA4B,SAAS;QAAC;QAAE;QAC1D,kBAAC,QAAD;SAAM,WAAU;mBAAqB,EAAK;SAAc,CAAA;QACtD,EAAA,CAAA;OACH,EAAK,gBACJ,kBAAC,KAAD,EAAA,UAAA;QACG,EAAc,GAAG,8BAA8B,WAAW;QAAC;QAAE;QAC9D,kBAAC,QAAD;SAAM,WAAU;mBAAqB,EAAK;SAAoB,CAAA;QAC5D,EAAA,CAAA;OAEN,kBAAC,KAAD,EAAA,UAAA;QACG,EAAc,GAAG,0BAA0B,OAAO;QAAC;QAAE;QACtD,kBAAC,QAAD;SAAM,WAAU;mBAAqB,EAAW,EAAK,UAAU;SAAQ,CAAA;QACrE,EAAA,CAAA;OACJ,kBAAC,KAAD,EAAA,UAAA;QACG,EAAc,GAAG,2BAA2B,QAAQ;QAAC;QAAE;QACxD,kBAAC,QAAD;SAAM,WAAU;mBAA+B,EAAK;SAAc,CAAA;QAChE,EAAA,CAAA;OACQ;QACJ;OAxBI,EAAK,GAwBT,CACZ;IACE,CAAA;GAEJ;MA3FJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAc,GAAG,6BAA6B,gBAAgB;IAC5D,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,GACA,wCACA,8CACD;IACC,CAAA,CACA;;EACF,CAAA"}
|
|
@@ -9,7 +9,7 @@ import { useEffect as o, useMemo as s } from "react";
|
|
|
9
9
|
import { AlertCircle as c, ArrowLeft as l } from "lucide-react";
|
|
10
10
|
import { jsx as u, jsxs as d } from "react/jsx-runtime";
|
|
11
11
|
import { Button as f, CardContent as p, CardDescription as m, CardHeader as h, CardTitle as ne, GlassCard as g, PagePurpose as _, Skeleton as v } from "@burdenoff/fe-libs/ui";
|
|
12
|
-
import { Link as y, useNavigate as b } from "react-router
|
|
12
|
+
import { Link as y, useNavigate as b } from "react-router";
|
|
13
13
|
import { useUpgradePrompt as x } from "@burdenoff/fe-libs/shared/hooks";
|
|
14
14
|
import { AccessDenied as S, UpgradeInlineBanner as C } from "@burdenoff/fe-libs/shared/components";
|
|
15
15
|
import { useI18n as w } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|