@burdenoff/microfe-workspaces 2026.917.1 → 2026.1010.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WorkspacesRoot.js +7 -9
- package/dist/WorkspacesRoutes.js +2 -4
- package/dist/components/CreateProjectDialog.js +0 -2
- package/dist/components/CreateWorkspaceDialog.js +0 -2
- package/dist/components/InviteMemberDialog.js +0 -2
- package/dist/components/MemberListItem.js +0 -2
- package/dist/components/ProjectCard.js +0 -2
- package/dist/components/ProjectMemberPickerDialog.js +0 -2
- package/dist/components/RenameProjectDialog.js +0 -2
- package/dist/components/WorkspaceCard.js +0 -2
- package/dist/components/WorkspaceContextPathSync.js +0 -2
- package/dist/components/WorkspacesErrorBoundary.js +0 -2
- package/dist/components/tags/ProjectTagsWidget.js +0 -2
- package/dist/constants/permissions.js +1 -3
- package/dist/generated/global-operations.js +0 -2
- package/dist/generated/wspace-operations.js +0 -2
- package/dist/hooks/useMemberMutations.js +0 -2
- package/dist/hooks/useOrgsWithPermission.js +0 -2
- package/dist/hooks/usePendingInvitations.js +0 -2
- package/dist/hooks/useProjectMemberMutations.js +0 -2
- package/dist/hooks/useProjectMembers.js +0 -2
- package/dist/hooks/useProjectMutations.js +0 -2
- package/dist/hooks/useProjects.js +0 -2
- package/dist/hooks/useScopedNavigation.js +0 -2
- package/dist/hooks/useTenants.js +0 -2
- package/dist/hooks/useTr.js +0 -2
- package/dist/hooks/useWorkspaceActivity.js +0 -2
- package/dist/hooks/useWorkspaceMembers.js +0 -2
- package/dist/hooks/useWorkspaceMutations.js +0 -2
- package/dist/hooks/useWorkspacePermissions.js +0 -2
- package/dist/hooks/useWorkspaceRoles.js +0 -2
- package/dist/hooks/useWorkspaces.js +0 -2
- package/dist/hooks/useWorkspacesEventEmitter.js +0 -2
- package/dist/hooks/useWorkspacesPageView.js +0 -2
- package/dist/pages/ActivityListPage.js +0 -2
- package/dist/pages/CreateWorkspacePage.js +0 -2
- package/dist/pages/InviteAcceptancePage.js +0 -2
- package/dist/pages/MembersListPage.js +0 -2
- package/dist/pages/PendingInvitesPage.js +0 -2
- package/dist/pages/ProjectDetailPage.js +0 -2
- package/dist/pages/ProjectMembersPage.js +0 -2
- package/dist/pages/ProjectsListPage.js +0 -2
- package/dist/pages/WorkspaceDetailPage.js +0 -2
- package/dist/pages/WorkspaceInvitePage.js +0 -2
- package/dist/pages/WorkspaceSettingsPage.js +0 -2
- package/dist/pages/WorkspacesListPage.js +0 -2
- package/dist/pages/dashboard/DashboardCustomizer.js +0 -2
- package/dist/pages/dashboard/DashboardGrid.js +0 -2
- package/dist/pages/dashboard/DashboardPage.js +0 -2
- package/dist/pages/dashboard/WidgetWrapper.js +0 -2
- package/dist/pages/dashboard/config.js +0 -2
- package/dist/pages/dashboard/hooks/useDashboardData.js +17 -13
- package/dist/pages/dashboard/hooks/useDashboardPreferences.js +0 -2
- package/dist/pages/dashboard/reorder.js +0 -2
- package/dist/pages/dashboard/widgets/AgentsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/GroupsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/MembersWidget.js +0 -2
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/QuickActionsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +0 -2
- package/dist/pages/dashboard/widgets/SessionsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/StorageWidget.js +0 -2
- package/dist/pages/dashboard/widgets/TagCloudWidget.js +0 -2
- package/dist/pages/dashboard/widgets/TipsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/VibesWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js +0 -2
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +0 -2
- package/dist/providers/WorkspacesProvider.js +0 -2
- package/dist/store/workspacesStore.js +0 -2
- package/dist/translations/en.js +0 -2
- package/dist/utils/formatters.js +0 -2
- package/dist/utils/i18n.js +0 -2
- package/dist/utils/invitationDisplay.js +0 -2
- package/dist/utils/projectTags.js +0 -2
- package/dist/utils/slug.js +0 -2
- package/dist/utils/telemetryError.js +0 -2
- package/dist/utils/tokenHelpers.js +0 -2
- package/dist/utils/workspaceBulkInviteCsv.js +0 -2
- package/package.json +3 -2
- package/dist/WorkspacesRoot.js.map +0 -1
- package/dist/WorkspacesRoutes.js.map +0 -1
- package/dist/components/CreateProjectDialog.js.map +0 -1
- package/dist/components/CreateWorkspaceDialog.js.map +0 -1
- package/dist/components/InviteMemberDialog.js.map +0 -1
- package/dist/components/MemberListItem.js.map +0 -1
- package/dist/components/ProjectCard.js.map +0 -1
- package/dist/components/ProjectMemberPickerDialog.js.map +0 -1
- package/dist/components/RenameProjectDialog.js.map +0 -1
- package/dist/components/WorkspaceCard.js.map +0 -1
- package/dist/components/WorkspaceContextPathSync.js.map +0 -1
- package/dist/components/WorkspacesErrorBoundary.js.map +0 -1
- package/dist/components/tags/ProjectTagsWidget.js.map +0 -1
- package/dist/constants/permissions.js.map +0 -1
- package/dist/generated/global-operations.js.map +0 -1
- package/dist/generated/wspace-operations.js.map +0 -1
- package/dist/hooks/useMemberMutations.js.map +0 -1
- package/dist/hooks/useOrgsWithPermission.js.map +0 -1
- package/dist/hooks/usePendingInvitations.js.map +0 -1
- package/dist/hooks/useProjectMemberMutations.js.map +0 -1
- package/dist/hooks/useProjectMembers.js.map +0 -1
- package/dist/hooks/useProjectMutations.js.map +0 -1
- package/dist/hooks/useProjects.js.map +0 -1
- package/dist/hooks/useScopedNavigation.js.map +0 -1
- package/dist/hooks/useTenants.js.map +0 -1
- package/dist/hooks/useTr.js.map +0 -1
- package/dist/hooks/useWorkspaceActivity.js.map +0 -1
- package/dist/hooks/useWorkspaceMembers.js.map +0 -1
- package/dist/hooks/useWorkspaceMutations.js.map +0 -1
- package/dist/hooks/useWorkspacePermissions.js.map +0 -1
- package/dist/hooks/useWorkspaceRoles.js.map +0 -1
- package/dist/hooks/useWorkspaces.js.map +0 -1
- package/dist/hooks/useWorkspacesEventEmitter.js.map +0 -1
- package/dist/hooks/useWorkspacesPageView.js.map +0 -1
- package/dist/pages/ActivityListPage.js.map +0 -1
- package/dist/pages/CreateWorkspacePage.js.map +0 -1
- package/dist/pages/InviteAcceptancePage.js.map +0 -1
- package/dist/pages/MembersListPage.js.map +0 -1
- package/dist/pages/PendingInvitesPage.js.map +0 -1
- package/dist/pages/ProjectDetailPage.js.map +0 -1
- package/dist/pages/ProjectMembersPage.js.map +0 -1
- package/dist/pages/ProjectsListPage.js.map +0 -1
- package/dist/pages/WorkspaceDetailPage.js.map +0 -1
- package/dist/pages/WorkspaceInvitePage.js.map +0 -1
- package/dist/pages/WorkspaceSettingsPage.js.map +0 -1
- package/dist/pages/WorkspacesListPage.js.map +0 -1
- package/dist/pages/dashboard/DashboardCustomizer.js.map +0 -1
- package/dist/pages/dashboard/DashboardGrid.js.map +0 -1
- package/dist/pages/dashboard/DashboardPage.js.map +0 -1
- package/dist/pages/dashboard/WidgetWrapper.js.map +0 -1
- package/dist/pages/dashboard/config.js.map +0 -1
- package/dist/pages/dashboard/hooks/useDashboardData.js.map +0 -1
- package/dist/pages/dashboard/hooks/useDashboardPreferences.js.map +0 -1
- package/dist/pages/dashboard/reorder.js.map +0 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/GroupsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/QuickActionsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/StorageWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/TagCloudWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/TipsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowRunsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkflowsOverviewWidget.js.map +0 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +0 -1
- package/dist/providers/WorkspacesProvider.js.map +0 -1
- package/dist/store/workspacesStore.js.map +0 -1
- package/dist/translations/en.js.map +0 -1
- package/dist/utils/formatters.js.map +0 -1
- package/dist/utils/i18n.js.map +0 -1
- package/dist/utils/invitationDisplay.js.map +0 -1
- package/dist/utils/projectTags.js.map +0 -1
- package/dist/utils/slug.js.map +0 -1
- package/dist/utils/telemetryError.js.map +0 -1
- package/dist/utils/tokenHelpers.js.map +0 -1
- package/dist/utils/workspaceBulkInviteCsv.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"PendingInvitesPage.js","names":[],"sources":["../../src/pages/PendingInvitesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router';\nimport { Mail, RefreshCw, CheckCircle, XCircle, AlertTriangle, Home } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n Badge,\n Button,\n GlassCard,\n PageContainer,\n Skeleton,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n getProjectDisplayNames,\n getRoleDisplayNames,\n getWorkspaceDisplayName,\n} from '../utils/invitationDisplay';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useAcceptInvitation, useRejectInvitation } from '../hooks/useMemberMutations';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nfunction formatDate(value?: string) {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function PendingInvitesPage() {\n useWorkspacesPageView('workspaces.pending-invites');\n\n const { t } = useI18n();\n const navigate = useNavigate();\n const {\n currentUser,\n authToken,\n apiGatewayUrl,\n organizationId,\n basePath = '/workspaces',\n } = useWorkspacesContext();\n const [processingInviteId, setProcessingInviteId] = useState<string | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const {\n data: pendingInvitations = [],\n isLoading,\n error,\n } = useMyPendingInvitations(currentUser?.email);\n\n const sortedInvitations = useMemo(\n () =>\n [...pendingInvitations].sort(\n (left, right) => new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime()\n ),\n [pendingInvitations]\n );\n\n const inviterActors = useMemo<ActorInput[]>(\n () =>\n sortedInvitations.flatMap((invitation) =>\n invitation.invitedById\n ? [{ actorId: invitation.invitedById, actorType: 'user' as const }]\n : []\n ),\n [sortedInvitations]\n );\n\n const inviterProfiles: ActorProfileMap = useActorProfiles(inviterActors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n });\n\n const { mutate: acceptInvitation, isPending: isAccepting } = useAcceptInvitation();\n const { mutate: rejectInvitation, isPending: isRejecting } = useRejectInvitation();\n\n const isMutating = isAccepting || isRejecting;\n\n const handleBackToWorkspaces = () => {\n navigate(`${basePath}/list`);\n };\n\n const handleAccept = (invitationId: string) => {\n setProcessingInviteId(invitationId);\n setActionError(null);\n\n acceptInvitation(invitationId, {\n onSuccess: () => {\n setProcessingInviteId(null);\n },\n onError: (mutationError) => {\n setProcessingInviteId(null);\n setActionError(\n mutationError instanceof Error\n ? mutationError.message\n : tWithFallback(t, 'pendingInvites.acceptError', 'Failed to accept invitation')\n );\n },\n });\n };\n\n const handleReject = (invitationId: string) => {\n setProcessingInviteId(invitationId);\n setActionError(null);\n\n rejectInvitation(invitationId, {\n onSuccess: () => {\n setProcessingInviteId(null);\n },\n onError: (mutationError) => {\n setProcessingInviteId(null);\n setActionError(\n mutationError instanceof Error\n ? mutationError.message\n : tWithFallback(t, 'pendingInvites.rejectError', 'Failed to reject invitation')\n );\n },\n });\n };\n\n if (!authToken || !currentUser?.email) {\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"rounded-lg border border-border-seam bg-bg-surface p-6 text-center\">\n <AlertTriangle className=\"mx-auto mb-3 size-10 text-text-secondary\" />\n <h1 className=\"text-2xl font-bold\">\n {tWithFallback(\n t,\n 'pendingInvites.signInTitle',\n 'Sign in to view your pending invites'\n )}\n </h1>\n <p className=\"mt-2 text-text-secondary\">\n {tWithFallback(\n t,\n 'pendingInvites.signInDescription',\n 'You need an authenticated account before you can review or respond to workspace invitations.'\n )}\n </p>\n <div className=\"mt-4 flex justify-center\">\n <Button onClick={handleBackToWorkspaces} variant=\"outline\">\n <Home className=\"mr-2 size-4\" />\n {tWithFallback(t, 'pendingInvites.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n </div>\n </div>\n </div>\n </PageContainer>\n );\n }\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'pendingInvites.pageTitle', 'Pending Invites')}\n </h1>\n <p className=\"text-sm sm:text-base text-text-secondary\">\n {tWithFallback(\n t,\n 'pendingInvites.pageSubtitle',\n 'Review and respond to workspace invitations sent to your account.'\n )}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{sortedInvitations.length}</Badge>\n <Button variant=\"outline\" onClick={handleBackToWorkspaces}>\n <Home className=\"mr-2 size-4\" />\n {tWithFallback(t, 'pendingInvites.viewWorkspaces', 'View Workspaces')}\n </Button>\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'pendingInvites.purpose',\n 'These are workspace invitations sent to your email but not yet answered. Accepting one adds you to that workspace (with the roles and project access shown); rejecting declines it. Invitations expire, so respond to the ones you want before their expiry date.'\n )}\n </PagePurpose>\n\n {actionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-status-error-border/30 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n <AlertTriangle className=\"mt-0.5 size-4 shrink-0\" />\n <span>{actionError}</span>\n </div>\n ) : null}\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border/30 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'pendingInvites.loadError', 'Failed to load pending invitations')}\n </div>\n ) : null}\n\n {isLoading ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 3 }).map((_, index) => (\n <div\n key={index}\n className=\"rounded-lg border border-border-seam bg-bg-surface p-5 space-y-3\"\n >\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"h-4 w-64\" />\n <Skeleton className=\"h-4 w-52\" />\n <div className=\"flex gap-2 pt-2\">\n <Skeleton className=\"h-10 w-36\" />\n <Skeleton className=\"h-10 w-28\" />\n </div>\n </div>\n ))}\n </div>\n ) : sortedInvitations.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tWithFallback(t, 'pendingInvites.emptyTitle', 'No pending invites')}\n description={tWithFallback(\n t,\n 'pendingInvites.emptyDescription',\n 'When someone invites you to a workspace, it will appear here.'\n )}\n />\n ) : (\n <div className=\"space-y-4\">\n {sortedInvitations.map((invitation, invitationIndex) => {\n const inviterProfile = invitation.invitedById\n ? inviterProfiles[invitation.invitedById]\n : undefined;\n const inviterLabel =\n inviterProfile?.displayName ||\n inviterProfile?.email ||\n invitation.invitedById ||\n '—';\n const expiresAt = formatDate(invitation.expiresAt);\n const createdAt = formatDate(invitation.createdAt);\n const workspaceDisplayName = getWorkspaceDisplayName(invitation);\n const roleDisplayNames = getRoleDisplayNames(invitation);\n const projectDisplayNames = getProjectDisplayNames(invitation);\n const isProcessingThisInvite = processingInviteId === invitation.id && isMutating;\n\n return (\n <GlassCard\n key={invitation.id}\n treatment=\"insight\"\n glow={invitationIndex === 0}\n className=\"border-border-seam p-5\"\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-3 min-w-0\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <div className=\"flex items-center gap-2 font-semibold text-lg\">\n <Mail className=\"size-4 text-primary\" />\n <span>{invitation.email}</span>\n </div>\n <Badge variant=\"secondary\">{invitation.status}</Badge>\n </div>\n\n <dl className=\"grid gap-2 text-sm text-text-secondary sm:grid-cols-2\">\n <div>\n <dt className=\"font-medium text-text-primary\">\n {tWithFallback(t, 'pendingInvites.workspaceLabel', 'Workspace')}\n </dt>\n <dd className=\"break-words font-medium text-text-primary\">\n {workspaceDisplayName}\n </dd>\n {invitation.workspaceId ? (\n <dd className=\"mt-1 break-all font-mono text-xs text-text-secondary\">\n {invitation.workspaceId}\n </dd>\n ) : null}\n </div>\n <div>\n <dt className=\"font-medium text-text-primary\">\n {tWithFallback(t, 'pendingInvites.invitedByLabel', 'Invited by')}\n </dt>\n <dd className=\"break-all\">\n {inviterProfile ? (\n <ActorIdentity\n profile={inviterProfile}\n variant=\"inline\"\n hideIdDrilldown\n />\n ) : (\n <span className=\"font-medium text-text-primary\">{inviterLabel}</span>\n )}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-primary\">\n {tWithFallback(t, 'pendingInvites.receivedLabel', 'Received')}\n </dt>\n <dd>{createdAt ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-text-primary\">\n {tWithFallback(t, 'pendingInvites.expiresLabel', 'Expires')}\n </dt>\n <dd>{expiresAt ?? '—'}</dd>\n </div>\n </dl>\n\n <div>\n <p className=\"text-sm font-medium text-text-primary mb-2\">\n {tWithFallback(t, 'pendingInvites.rolesLabel', 'Roles')}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {roleDisplayNames.length > 0 ? (\n roleDisplayNames.map((roleName, index) => (\n <Badge\n key={`${invitation.id}-${invitation.roleIds[index] ?? roleName}`}\n variant=\"outline\"\n >\n {roleName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'pendingInvites.noRoles',\n 'No explicit roles assigned'\n )}\n </span>\n )}\n </div>\n </div>\n\n <div>\n <p className=\"text-sm font-medium text-text-primary mb-2\">\n {tWithFallback(t, 'pendingInvites.projectsLabel', 'Projects')}\n </p>\n <div className=\"flex flex-wrap gap-2\">\n {projectDisplayNames.length > 0 ? (\n projectDisplayNames.map((projectName, index) => (\n <Badge\n key={`${invitation.id}-project-${projectName}-${index}`}\n variant=\"secondary\"\n >\n {projectName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'pendingInvites.noProjects',\n 'No project access will be granted automatically'\n )}\n </span>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row lg:flex-col lg:min-w-44\">\n <Button\n onClick={() => handleAccept(invitation.id)}\n disabled={isMutating}\n className=\"w-full\"\n >\n {isProcessingThisInvite && isAccepting ? (\n <RefreshCw className=\"mr-2 size-4 animate-spin\" />\n ) : (\n <CheckCircle className=\"mr-2 size-4\" />\n )}\n {tWithFallback(t, 'pendingInvites.acceptButton', 'Accept')}\n </Button>\n <Button\n variant=\"outline\"\n onClick={() => handleReject(invitation.id)}\n disabled={isMutating}\n className=\"w-full\"\n >\n {isProcessingThisInvite && isRejecting ? (\n <RefreshCw className=\"mr-2 size-4 animate-spin\" />\n ) : (\n <XCircle className=\"mr-2 size-4\" />\n )}\n {tWithFallback(t, 'pendingInvites.rejectButton', 'Reject')}\n </Button>\n </div>\n </div>\n </GlassCard>\n );\n })}\n </div>\n )}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectDetailPage.js","names":[],"sources":["../../src/pages/ProjectDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport {\n ArrowLeft,\n FolderKanban,\n Users,\n UserPlus,\n Activity,\n Archive,\n MoreVertical,\n Clock,\n Pencil,\n} from 'lucide-react';\nimport {\n Button,\n ShowMore,\n GlassCard,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n DropdownMenuSeparator,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n EntityIdModal,\n useEntityIdModal,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n} from '@burdenoff/fe-libs/ui';\nimport { type TagItem } from '@burdenoff/fe-libs/tag';\nimport { ProjectTagsWidget } from '../components/tags/ProjectTagsWidget';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useProject, useProjectMembersCount } from '../hooks/useProjects';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport {\n useArchiveProject,\n useUnarchiveProject,\n useDeleteProject,\n useTagProject,\n useUntagProject,\n useUpdateProject,\n} from '../hooks/useProjectMutations';\nimport { formatDate } from '../utils/formatters';\nimport { nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { getProjectDisplayTags, toProjectTag } from '../utils/projectTags';\nimport { getProjectStatusClasses } from '../utils/tokenHelpers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\nimport { RenameProjectDialog } from '../components/RenameProjectDialog';\n\nexport function ProjectDetailPage() {\n const { t } = useI18n();\n const idModal = useEntityIdModal();\n\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n useWorkspacesPageView('workspaces.project-detail', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n const navigate = useNavigate();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { data: project, isLoading, error } = useProject(projectId || '', workspaceId);\n const [showDeleteDialog, setShowDeleteDialog] = useState(false);\n const [showAddMemberDialog, setShowAddMemberDialog] = useState(false);\n const [showRenameDialog, setShowRenameDialog] = useState(false);\n\n const { mutate: archiveProject, isPending: isArchiving } = useArchiveProject(workspaceId);\n const { mutate: unarchiveProject, isPending: isUnarchiving } = useUnarchiveProject(workspaceId);\n const { mutate: deleteProject, isPending: isDeleting } = useDeleteProject(workspaceId);\n const { mutateAsync: tagProject, isPending: isTaggingProject } = useTagProject(workspaceId);\n const { mutateAsync: untagProject, isPending: isUntaggingProject } = useUntagProject(workspaceId);\n const { mutateAsync: updateProject, isPending: isUpdatingProject } =\n useUpdateProject(workspaceId);\n const {\n canViewProject,\n canUpdateProject,\n canDeleteProject,\n canArchiveProject,\n canListProjectMembers,\n canAddProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n const { data: projectMemberCount } = useProjectMembersCount(projectId || '', workspaceId);\n const displayTags = useMemo(\n () =>\n getProjectDisplayTags({\n tags: project?.tags ?? [],\n tagAssignments: project?.tagAssignments,\n }),\n [project?.tags, project?.tagAssignments]\n );\n const { data: workspaceActivity } = useWorkspaceActivity(workspaceId);\n const relatedActivityCount = (workspaceActivity ?? []).filter(\n (item) => item.resourceId === projectId || item.resourceType?.toLowerCase() === 'project'\n ).length;\n const widgetTags: TagItem[] = displayTags;\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 (!canViewProject) {\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, 'projectDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'projectDetail.noPermissionToViewProject',\n \"You don't have permission to view this project.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n };\n\n const handleArchive = () => {\n if (!project) return;\n if (project.archived) {\n unarchiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to unarchive project:', err);\n },\n });\n } else {\n archiveProject(project.id, {\n onError: (err) => {\n console.error('Failed to archive project:', err);\n },\n });\n }\n };\n\n const handleDelete = () => {\n if (!project) return;\n void nativeImpact('medium');\n deleteProject(project.id, {\n onSuccess: () => {\n void nativeNotify('success');\n setShowDeleteDialog(false);\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`));\n },\n onError: (err) => {\n void nativeNotify('error');\n console.error('Failed to delete project:', err);\n },\n });\n };\n\n const handleViewProjectMembers = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}/members`));\n };\n\n const syncLegacyProjectTags = async (nextTags: TagItem[]) => {\n if (!project) {\n return;\n }\n\n const normalizedNextTags = nextTags.map((tag) => toProjectTag(tag));\n\n try {\n await updateProject({\n id: project.id,\n input: {\n tags: normalizedNextTags,\n },\n });\n } catch (err) {\n void nativeNotify('warning');\n console.error(\n 'Failed to sync legacy project tag mirror after updating structured project tags:',\n err\n );\n }\n };\n\n const handleAddTag = async (tag: TagItem) => {\n if (!project) {\n return;\n }\n\n try {\n await tagProject({\n entityId: project.id,\n tagId: tag.id,\n });\n\n await syncLegacyProjectTags([...displayTags, toProjectTag(tag)]);\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n const handleRemoveTag = async (tagId: string) => {\n if (!project) {\n return;\n }\n\n const assignmentId = project.tagAssignments?.find(\n (assignment) => assignment.tagId === tagId\n )?.id;\n try {\n await untagProject({\n entityId: project.id,\n tagId,\n assignmentId,\n });\n\n await syncLegacyProjectTags(\n displayTags.filter((tag) => tag.tagId !== tagId && tag.id !== tagId)\n );\n } catch (err) {\n void nativeNotify('error');\n throw err;\n }\n };\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-32\" />\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n <Skeleton className=\"h-48\" />\n </div>\n );\n }\n\n if (error || !project) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tWithFallback(t, 'projectDetail.projectNotFound', 'Project not found')}\n description={tWithFallback(\n t,\n 'projectDetail.projectNotFoundDescription',\n \"The project you're looking for doesn't exist or you don't have permission to view it.\"\n )}\n action={\n <Button onClick={handleBack}>\n <ArrowLeft size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n }\n />\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"gap-2 mb-2 -ml-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectDetail.backToProjects', 'Back to Projects')}\n </Button>\n <div className=\"flex items-center gap-3\">\n <div className=\"p-3 rounded-lg bg-action-primary-bg/10\">\n <FolderKanban className=\"text-primary\" size={28} />\n </div>\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{project.name}</h1>\n <p className=\"text-sm text-text-secondary font-mono\">{project.key}</p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 mt-3\">\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 {tWithFallback(t, 'projectDetail.archived', 'Archived')}\n </Badge>\n )}\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n variant=\"outline\"\n size=\"icon\"\n aria-label={tWithFallback(\n t,\n 'projectDetail.openProjectActionsAriaLabel',\n 'Open project actions'\n )}\n >\n <MoreVertical size={20} />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {canUpdateProject && (\n <DropdownMenuItem onClick={() => setShowRenameDialog(true)}>\n <Pencil size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.renameProject', 'Rename Project')}\n </DropdownMenuItem>\n )}\n {canUpdateProject && (canArchiveProject || canDeleteProject) && (\n <DropdownMenuSeparator />\n )}\n {canArchiveProject && (\n <DropdownMenuItem onClick={handleArchive} disabled={isArchiving || isUnarchiving}>\n <Archive size={16} className=\"mr-2\" />\n {isArchiving || isUnarchiving\n ? tWithFallback(t, 'projectDetail.processing', 'Processing...')\n : project.archived\n ? tWithFallback(t, 'projectDetail.unarchive', 'Unarchive')\n : tWithFallback(t, 'projectDetail.archive', 'Archive')}\n </DropdownMenuItem>\n )}\n {canArchiveProject && canDeleteProject && <DropdownMenuSeparator />}\n {canDeleteProject && (\n <DropdownMenuItem\n onClick={() => setShowDeleteDialog(true)}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'projectDetail.purpose',\n 'This is a single project — a focused stream of work within its workspace. From here you can manage the people working on it, organise it with tags so it surfaces in filters and reports, and keep track of related activity. Use the menu in the top-right to archive or delete it when it wraps up.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"workspaces-project-detail\"\n steps={[\n ...(canAddProjectMember\n ? [\n {\n id: 'add-member',\n label: tWithFallback(\n t,\n 'projectDetail.stepAddMember',\n 'Add people to the project'\n ),\n description: tWithFallback(\n t,\n 'projectDetail.stepAddMemberDesc',\n 'Pull in the teammates who will actually work on this project.'\n ),\n icon: <UserPlus size={16} />,\n onClick: () => setShowAddMemberDialog(true),\n },\n ]\n : []),\n {\n id: 'tag-project',\n label: tWithFallback(t, 'projectDetail.stepTag', 'Tag the project'),\n description: tWithFallback(\n t,\n 'projectDetail.stepTagDesc',\n 'Add tags so this project shows up in the right filters and views.'\n ),\n icon: <FolderKanban size={16} />,\n onClick: () => {\n document\n .getElementById('project-tags-section')\n ?.scrollIntoView({ behavior: 'smooth', block: 'center' });\n },\n },\n ...(canListProjectMembers\n ? [\n {\n id: 'manage-members',\n label: tWithFallback(\n t,\n 'projectDetail.stepManageMembers',\n 'Review project members'\n ),\n description: tWithFallback(\n t,\n 'projectDetail.stepManageMembersDesc',\n 'See who has access and adjust their project roles.'\n ),\n icon: <Users size={16} />,\n onClick: handleViewProjectMembers,\n },\n ]\n : []),\n {\n id: 'review-activity',\n label: tWithFallback(t, 'projectDetail.stepActivity', 'Check recent activity'),\n description: tWithFallback(\n t,\n 'projectDetail.stepActivityDesc',\n 'Catch up on what has changed across the workspace.'\n ),\n icon: <Activity size={16} />,\n onClick: () => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`)),\n },\n ]}\n />\n\n {/* Main + detail rail */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Main column */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n {/* Stat metrics */}\n <div className=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <GlassCard treatment=\"metric\" glow>\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-sm font-medium text-text-secondary\">\n <Users size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'projectDetail.membersCardTitle', 'Members')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <p className=\"text-3xl font-bold tabular-nums\">{projectMemberCount ?? 0}</p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'projectDetail.projectMembers', 'Project members')}\n </p>\n\n {canAddProjectMember || canListProjectMembers ? (\n <div className=\"flex flex-wrap gap-2\">\n {canAddProjectMember ? (\n <Button size=\"sm\" onClick={() => setShowAddMemberDialog(true)}>\n <UserPlus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.addProjectMember', 'Add Member')}\n </Button>\n ) : null}\n\n {canListProjectMembers ? (\n <Button variant=\"outline\" size=\"sm\" onClick={handleViewProjectMembers}>\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.manageProjectMembers', 'Manage Members')}\n </Button>\n ) : null}\n </div>\n ) : null}\n </CardContent>\n </GlassCard>\n\n <GlassCard treatment=\"metric\">\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-sm font-medium text-text-secondary\">\n <Activity size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'projectDetail.activityCardTitle', 'Activity')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">{relatedActivityCount}</p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'projectDetail.recentActivityItems', 'Recent activity items')}\n </p>\n </CardContent>\n </GlassCard>\n </div>\n\n <GlassCard treatment=\"plain\" id=\"project-tags-section\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'projectDetail.tagsTitle', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(\n t,\n 'projectDetail.tagsDescription',\n 'Use universal tags to classify and filter this project across the workspace.'\n )}\n </p>\n <ProjectTagsWidget\n entityType=\"Project\"\n entityId={project.id}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n className={\n isTaggingProject || isUntaggingProject || isUpdatingProject\n ? 'pointer-events-none opacity-75'\n : undefined\n }\n />\n </CardContent>\n </GlassCard>\n </div>\n\n {/* Detail rail */}\n <aside className=\"lg:sticky lg:top-6 lg:self-start\">\n {/* Project Information */}\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'projectDetail.projectInformation', 'Project Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-4\">\n <div className=\"grid grid-cols-1 gap-4 text-sm\">\n <div className=\"space-y-1\">\n <span className=\"text-text-muted flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectId', 'Project ID')}\n </span>\n <button\n type=\"button\"\n onClick={() =>\n idModal.show({\n id: project.id,\n name: project.name,\n type: 'Project',\n details: project.key ? [{ label: 'Key', value: project.key }] : undefined,\n })\n }\n className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-link hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--color-focus-ring)] rounded\"\n >\n {tWithFallback(t, 'projectDetail.getId', 'Get ID')}\n </button>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <FolderKanban size={14} />\n {tWithFallback(t, 'projectDetail.projectKey', 'Project Key')}\n </span>\n <span className=\"font-medium\">{project.key}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.created', 'Created')}\n </span>\n <span>{formatDate(project.createdAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Clock size={14} />\n {tWithFallback(t, 'projectDetail.lastUpdated', 'Last Updated')}\n </span>\n <span>{formatDate(project.updatedAt)}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.workspaceId', 'Workspace ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.workspaceId}</span>\n </div>\n\n <div className=\"space-y-1\">\n <span className=\"text-text-secondary flex items-center gap-2\">\n <Users size={14} />\n {tWithFallback(t, 'projectDetail.creatorId', 'Creator ID')}\n </span>\n <span className=\"font-mono text-xs block\">{project.creatorId}</span>\n </div>\n </div>\n\n {project.config && Object.keys(project.config).length > 0 && (\n <div className=\"pt-4 border-t border-border-seam\">\n <h4 className=\"text-sm font-medium mb-2\">\n {tWithFallback(t, 'projectDetail.configuration', 'Configuration')}\n </h4>\n <pre className=\"text-xs bg-bg-sunken p-3 rounded-lg overflow-auto\">\n {JSON.stringify(project.config, null, 2)}\n </pre>\n </div>\n )}\n\n {/* Quick Actions — progressive disclosure (secondary navigation) */}\n <ShowMore\n collapsedHeight={0}\n label={tWithFallback(t, 'projectDetail.quickActions', 'Quick Actions')}\n >\n <div className=\"flex flex-col gap-2 pt-2\">\n {canListProjectMembers ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={handleViewProjectMembers}\n >\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.projectMembersPage', 'Project Members')}\n </Button>\n ) : null}\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))\n }\n >\n <Users size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceMembers', 'Workspace Members')}\n </Button>\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"justify-start\"\n onClick={() =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))\n }\n >\n <Activity size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectDetail.workspaceActivity', 'Workspace Activity')}\n </Button>\n </div>\n </ShowMore>\n </CardContent>\n </GlassCard>\n </aside>\n </div>\n\n {/* Delete Confirmation Dialog */}\n <AlertDialog open={showDeleteDialog} onOpenChange={setShowDeleteDialog}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tWithFallback(t, 'projectDetail.deleteDialogTitle', 'Delete Project')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tWithFallback(\n t,\n 'projectDetail.deleteConfirmPrefix',\n 'Are you sure you want to delete \"'\n ) +\n project.name +\n tWithFallback(\n t,\n 'projectDetail.deleteConfirmSuffix',\n '\"? This action cannot be undone and all project data will be permanently removed.'\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isDeleting}>\n {tWithFallback(t, 'projectDetail.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n onClick={handleDelete}\n disabled={isDeleting}\n className=\"bg-status-error-bg text-status-error-text hover:bg-status-error-bg/90\"\n >\n {isDeleting\n ? tWithFallback(t, 'projectDetail.deleting', 'Deleting...')\n : tWithFallback(t, 'projectDetail.deleteProject', 'Delete Project')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n {workspaceId && projectId && showAddMemberDialog ? (\n <ProjectMemberPickerDialog\n open={showAddMemberDialog}\n onOpenChange={setShowAddMemberDialog}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project.name}\n />\n ) : null}\n {showRenameDialog && (\n <RenameProjectDialog\n project={{ id: project.id, name: project.name, workspaceId: project.workspaceId }}\n open={showRenameDialog}\n onOpenChange={setShowRenameDialog}\n />\n )}\n <EntityIdModal control={idModal} />\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectMembersPage.js","names":[],"sources":["../../src/pages/ProjectMembersPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport { ArrowLeft, Search, UserMinus, UserPlus } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport {\n Badge,\n Button,\n Input,\n PageContainer,\n Skeleton,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { ProjectMemberPickerDialog } from '../components/ProjectMemberPickerDialog';\nimport { useInfiniteProjectMembers } from '../hooks/useProjectMembers';\nimport { useProject } from '../hooks/useProjects';\nimport { useRemoveProjectMember } from '../hooks/useProjectMemberMutations';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { tWithFallback } from '../utils/i18n';\nimport { nativeConfirm, nativeImpact, nativeNotify } from '../utils/nativeBridge';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport type { ProjectMember } from '../types';\n\nconst PROJECT_MEMBER_PAGE_SIZE = 100;\n\nfunction getMemberFallback(profileMap: ActorProfileMap, member: ProjectMember) {\n const profile = profileMap[member.userId];\n if (profile) {\n return {\n name: profile.displayName,\n email: profile.email ?? undefined,\n };\n }\n\n return {\n name: member.userId,\n email: undefined,\n };\n}\n\nexport function ProjectMembersPage() {\n const { t } = useI18n();\n const navigate = useNavigate();\n const { workspaceId, projectId } = useParams<{ workspaceId: string; projectId: string }>();\n const {\n basePath = '/workspaces',\n apiGatewayUrl,\n authToken,\n organizationId,\n } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const [searchValue, setSearchValue] = useState('');\n const [isAddDialogOpen, setIsAddDialogOpen] = useState(false);\n const [removingUserId, setRemovingUserId] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<string | null>(null);\n\n useWorkspacesPageView('workspaces.project-members', {\n workspaceId,\n projectId,\n requireParams: true,\n });\n\n const {\n data: project,\n isLoading: isLoadingProject,\n error: projectError,\n } = useProject(projectId || '', workspaceId);\n const {\n data: projectMembersPages,\n isLoading: isLoadingMembers,\n error: membersError,\n hasNextPage: hasMoreProjectMembers,\n fetchNextPage: fetchNextProjectMembersPage,\n isFetchingNextPage: isFetchingNextProjectMembersPage,\n } = useInfiniteProjectMembers(projectId || '', workspaceId, PROJECT_MEMBER_PAGE_SIZE);\n const { mutate: removeProjectMember, isPending: isRemovingProjectMember } =\n useRemoveProjectMember(workspaceId);\n\n const {\n canViewProject,\n canListProjectMembers,\n canAddProjectMember,\n canRemoveProjectMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n const projectMembers = useMemo(\n () => projectMembersPages?.pages.flatMap((page) => page.items) ?? [],\n [projectMembersPages?.pages]\n );\n const totalProjectMembers = projectMembersPages?.pages[0]?.total ?? projectMembers.length;\n const actors = useMemo<ActorInput[]>(\n () => projectMembers.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [projectMembers]\n );\n const profileMap: ActorProfileMap = useActorProfiles(actors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n const filteredMembers = useMemo(() => {\n const needle = searchValue.trim().toLowerCase();\n if (!needle) {\n return projectMembers;\n }\n\n return projectMembers.filter((member) => {\n const fallback = getMemberFallback(profileMap, member);\n return (\n fallback.name.toLowerCase().includes(needle) ||\n fallback.email?.toLowerCase().includes(needle) ||\n member.userId.toLowerCase().includes(needle)\n );\n });\n }, [profileMap, projectMembers, searchValue]);\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-40\" />\n <Skeleton className=\"h-10 w-64\" />\n <Skeleton className=\"h-40 w-full\" />\n </div>\n );\n }\n\n if (!canViewProject || !canListProjectMembers) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'projectMembers.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'projectMembers.noPermission',\n \"You don't have permission to manage project members.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleBack = () => {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects/${projectId}`));\n };\n\n const handleRemoveMember = async (member: ProjectMember) => {\n const fallback = getMemberFallback(profileMap, member);\n void nativeImpact('medium');\n setRemoveError(null);\n const message = `${tWithFallback(t, 'projectMembers.removeConfirmPrefix', 'Remove')} ${fallback.name} ${tWithFallback(t, 'projectMembers.removeConfirmSuffix', 'from this project?')}`;\n const native = await nativeConfirm({\n title: tWithFallback(t, 'projectMembers.removeConfirmTitle', 'Remove project member'),\n message,\n okButtonTitle: tWithFallback(t, 'projectMembers.removeAction', 'Remove'),\n cancelButtonTitle: tWithFallback(t, 'projectMembers.cancel', 'Cancel'),\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) {\n return;\n }\n\n setRemovingUserId(member.userId);\n removeProjectMember(\n { projectId: member.projectId, userId: member.userId },\n {\n onSuccess: () => {\n void nativeNotify('success');\n setRemovingUserId(null);\n },\n onError: () => {\n void nativeNotify('error');\n setRemoveError(\n tWithFallback(\n t,\n 'projectMembers.removeError',\n 'Failed to remove project member. Please try again.'\n )\n );\n setRemovingUserId(null);\n },\n }\n );\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 p-3 sm:space-y-6 sm:p-6\">\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button variant=\"ghost\" onClick={handleBack} className=\"mb-2 -ml-2 gap-2\">\n <ArrowLeft size={20} />\n {tWithFallback(t, 'projectMembers.backToProject', 'Back to Project')}\n </Button>\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'projectMembers.pageTitle', 'Project Members')}\n </h1>\n <p className=\"text-sm text-text-secondary sm:text-base\">\n {project?.name\n ? `${project.name} · ${tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}`\n : tWithFallback(\n t,\n 'projectMembers.pageSubtitle',\n 'Only existing workspace members can be added to this project.'\n )}\n </p>\n </div>\n\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\">{totalProjectMembers}</Badge>\n {canAddProjectMember ? (\n <Button\n onClick={() => setIsAddDialogOpen(true)}\n disabled={!workspaceId || !projectId}\n >\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addMember', 'Add Member')}\n </Button>\n ) : null}\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'projectMembers.purpose',\n 'This is the subset of workspace members who can work on this specific project. Add people here to give them project-level access, search to find someone, or remove members who have moved on. Only existing workspace members can be added.'\n )}\n </PagePurpose>\n\n <div className=\"relative\">\n <Search className=\"absolute left-3 top-1/2 size-4 -translate-y-1/2 text-text-secondary\" />\n <Input\n value={searchValue}\n onChange={(event) => setSearchValue(event.target.value)}\n placeholder={tWithFallback(\n t,\n 'projectMembers.searchPlaceholder',\n 'Search project members'\n )}\n className=\"pl-9\"\n />\n </div>\n\n {removeError ? (\n <div className=\"rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {removeError}\n </div>\n ) : null}\n\n {isLoadingProject || isLoadingMembers ? (\n <div className=\"space-y-4\">\n {Array.from({ length: 4 }).map((_, index) => (\n <div key={index} className=\"rounded-lg border border-border-seam bg-bg-surface p-4\">\n <Skeleton className=\"h-5 w-40\" />\n <Skeleton className=\"mt-2 h-4 w-56\" />\n </div>\n ))}\n </div>\n ) : projectError || membersError ? (\n <div className=\"rounded-lg border border-status-error-border/20 bg-status-error-bg/10 p-4 text-sm text-status-error-text\">\n {(membersError instanceof Error && membersError.message) ||\n (projectError instanceof Error && projectError.message) ||\n tWithFallback(t, 'projectMembers.loadError', 'Failed to load project members')}\n </div>\n ) : filteredMembers.length === 0 ? (\n <IllustratedEmptyState\n illustration={searchValue ? 'empty-search' : 'empty-people'}\n title={\n searchValue\n ? tWithFallback(t, 'projectMembers.noResultsTitle', 'No matching members found')\n : tWithFallback(t, 'projectMembers.emptyTitle', 'No project members yet')\n }\n description={\n searchValue\n ? tWithFallback(\n t,\n 'projectMembers.noResultsSubtitle',\n 'Try a different search to find a project member.'\n )\n : tWithFallback(\n t,\n 'projectMembers.emptySubtitle',\n 'Add workspace members to this project so they can collaborate here.'\n )\n }\n action={\n hasMoreProjectMembers || (!searchValue && canAddProjectMember) ? (\n <div className=\"flex flex-wrap items-center justify-center gap-2\">\n {hasMoreProjectMembers ? (\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n ) : null}\n {!searchValue && canAddProjectMember ? (\n <Button onClick={() => setIsAddDialogOpen(true)}>\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'projectMembers.addFirstMember', 'Add First Member')}\n </Button>\n ) : null}\n </div>\n ) : undefined\n }\n />\n ) : (\n <div className=\"space-y-3\">\n {filteredMembers.map((member) => {\n const profile = profileMap[member.userId];\n const fallback = getMemberFallback(profileMap, member);\n const isRemoving = isRemovingProjectMember && removingUserId === member.userId;\n\n return (\n <div\n key={member.id}\n className=\"flex items-center justify-between gap-4 rounded-card border border-border-seam bg-bg-surface p-4 shadow-elevation-1 transition-[box-shadow,transform] duration-200 ease-[var(--motion-easing-out)] hover:shadow-elevation-2 [@media(hover:none)]:\"\n >\n <div className=\"min-w-0 flex-1\">\n {profile ? (\n <ActorIdentity profile={profile} variant=\"cell\" />\n ) : (\n <div className=\"min-w-0\">\n <p className=\"truncate font-medium\">{fallback.name}</p>\n {fallback.email ? (\n <p className=\"truncate text-sm text-text-secondary\">{fallback.email}</p>\n ) : (\n <p className=\"truncate text-xs text-text-secondary\">{member.userId}</p>\n )}\n </div>\n )}\n\n <div className=\"mt-1 flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-xs\">\n {tWithFallback(t, 'projectMembers.memberBadge', 'Project member')}\n </Badge>\n <span className=\"text-xs text-text-secondary\">\n {tWithFallback(t, 'projectMembers.addedLabel', 'Added')}{' '}\n {formatRelativeTime(member.createdAt)}\n </span>\n </div>\n </div>\n\n {canRemoveProjectMember ? (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => void handleRemoveMember(member)}\n disabled={isRemovingProjectMember}\n >\n <UserMinus className=\"mr-2 size-4\" />\n {isRemoving\n ? tWithFallback(t, 'projectMembers.removing', 'Removing...')\n : tWithFallback(t, 'projectMembers.removeAction', 'Remove')}\n </Button>\n ) : null}\n </div>\n );\n })}\n\n {hasMoreProjectMembers ? (\n <div className=\"flex flex-col items-center gap-2 rounded-lg border border-dashed border-border-seam bg-bg-sunken/20 p-4 text-center\">\n <p className=\"text-sm text-text-secondary\">\n Showing {projectMembers.length} of {totalProjectMembers} project members.\n </p>\n <Button\n variant=\"outline\"\n onClick={() => void fetchNextProjectMembersPage()}\n disabled={isFetchingNextProjectMembersPage}\n >\n {isFetchingNextProjectMembersPage ? 'Loading more…' : 'Load More Members'}\n </Button>\n </div>\n ) : null}\n </div>\n )}\n\n {workspaceId && projectId && isAddDialogOpen ? (\n <ProjectMemberPickerDialog\n open={isAddDialogOpen}\n onOpenChange={setIsAddDialogOpen}\n workspaceId={workspaceId}\n projectId={projectId}\n projectName={project?.name}\n />\n ) : null}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"ProjectsListPage.js","names":[],"sources":["../../src/pages/ProjectsListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useParams } from 'react-router';\nimport { Plus, Grid3x3, List, Search } from 'lucide-react';\nimport { TagBadge } from '@burdenoff/fe-libs/tag';\nimport {\n Button,\n Input,\n Card,\n CardContent,\n GlassCard,\n PageContainer,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Skeleton,\n IllustratedEmptyState,\n PagePurpose,\n} from '@burdenoff/fe-libs/ui';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { useMyProjects } from '../hooks/useProjects';\nimport {\n useArchiveProject,\n useDeleteProject,\n useUnarchiveProject,\n} from '../hooks/useProjectMutations';\nimport { ProjectCard } from '../components/ProjectCard';\nimport { CreateProjectDialog } from '../components/CreateProjectDialog';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { tWithFallback } from '../utils/i18n';\nimport { getProjectDisplayTags } from '../utils/projectTags';\nimport type { ProjectTag, Workspace } from '../types';\n\nexport function ProjectsListPage() {\n const { t } = useI18n();\n\n // The URL path `:workspaceId` (when present) intentionally OVERRIDES the active\n // context — it scopes this page to a specific workspace for deep-links/drill-in.\n // The cross-workspace route (`/workspaces/projects`, no path param) leaves it\n // undefined so the list stays cross-workspace. The stale-path-after-context-switch\n // problem is handled by WorkspaceContextPathSync (it rewrites this segment when the\n // active context changes), so the override never points at a stale workspace.\n // (BOFF-2805 follow-up)\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.projects', { workspaceId, requireParams: true });\n const {\n viewMode,\n setViewMode,\n projectFilters,\n setProjectFilters,\n clearProjectFilters,\n selectedProjectIds,\n toggleProjectSelection,\n clearProjectSelection,\n openCreateProjectDialog,\n isCreateProjectDialogOpen,\n } = useWorkspacesStore();\n\n const [searchValue, setSearchValue] = useState(projectFilters.search || '');\n const [activeTagFilters, setActiveTagFilters] = useState<ProjectTag[]>([]);\n const [matchAllTagFilters, setMatchAllTagFilters] = useState(true);\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n setProjectFilters({ search: debouncedSearch });\n }, [debouncedSearch, setProjectFilters]);\n\n useEffect(() => {\n if (activeTagFilters.length === 0) {\n setProjectFilters({\n tagIds: undefined,\n tagHierarchyPaths: undefined,\n matchAllTags: undefined,\n includeDescendants: undefined,\n });\n return;\n }\n\n setProjectFilters({\n tagIds: activeTagFilters.map((tag) => tag.tagId),\n tagHierarchyPaths: activeTagFilters\n .map((tag) => tag.hierarchyPath)\n .filter((tagPath): tagPath is string => typeof tagPath === 'string' && tagPath.length > 0),\n matchAllTags: matchAllTagFilters,\n includeDescendants: true,\n });\n }, [activeTagFilters, matchAllTagFilters, setProjectFilters]);\n\n useEffect(() => {\n return () => clearProjectSelection();\n }, [clearProjectSelection]);\n\n // Membership-scoped query: backend filters by actorId, so no client-side\n // permission gate is needed. Page is shared by `/workspaces/projects` and\n // `/workspaces/:workspaceId/projects`; the latter narrows the list below.\n const PROJECTS_PAGE_SIZE = 500;\n const {\n data: myData,\n isLoading,\n error,\n } = useMyProjects({\n limit: PROJECTS_PAGE_SIZE,\n offset: 0,\n });\n\n const data = useMemo(() => {\n const all = myData?.items ?? [];\n const q = (projectFilters.search ?? '').trim().toLowerCase();\n const tagIds = projectFilters.tagIds ?? [];\n const matchAllTags = projectFilters.matchAllTags ?? true;\n\n const matchesWorkspace = (project: (typeof all)[number]) =>\n !workspaceId || project.workspaceId === workspaceId;\n const matchesSearch = (project: (typeof all)[number]) =>\n !q || project.name.toLowerCase().includes(q) || project.key.toLowerCase().includes(q);\n const matchesStatus = (project: (typeof all)[number]) =>\n !projectFilters.status || project.status === projectFilters.status;\n const matchesArchived = (project: (typeof all)[number]) =>\n typeof projectFilters.archived === 'undefined' ||\n project.archived === projectFilters.archived;\n const matchesTags = (project: (typeof all)[number]) => {\n if (tagIds.length === 0) return true;\n const projectTagIds = new Set(getProjectDisplayTags(project).map((t) => t.tagId));\n return matchAllTags\n ? tagIds.every((id) => projectTagIds.has(id))\n : tagIds.some((id) => projectTagIds.has(id));\n };\n\n const filtered = all.filter(\n (project) =>\n matchesWorkspace(project) &&\n matchesSearch(project) &&\n matchesStatus(project) &&\n matchesArchived(project) &&\n matchesTags(project)\n );\n\n // Surface the real backend totals so the \"Showing X of Y\" footer can flag\n // truncation. For narrowed views (workspace/tag filter), the backend total\n // doesn't reflect the post-filter count, so we report the filtered size as\n // total. But we MUST preserve `hasMore` from upstream — if the upstream\n // query was capped (hasMore=true), matches outside the first page are\n // silently hidden, and the footer needs to keep flagging that.\n const apiTotal = myData?.total ?? filtered.length;\n const apiHasMore = myData?.hasMore ?? false;\n const isNarrowed = !!workspaceId || tagIds.length > 0;\n return {\n items: filtered,\n total: isNarrowed ? filtered.length : apiTotal,\n hasMore: apiHasMore,\n };\n }, [myData?.items, myData?.total, myData?.hasMore, projectFilters, workspaceId]);\n\n const { mutateAsync: archiveProject } = useArchiveProject(workspaceId);\n const { mutateAsync: unarchiveProject } = useUnarchiveProject(workspaceId);\n const { mutateAsync: deleteProject } = useDeleteProject(workspaceId);\n\n const selectedProjects = useMemo(\n () => data?.items.filter((project) => selectedProjectIds.has(project.id)) ?? [],\n [data?.items, selectedProjectIds]\n );\n\n const archivableProjects = useMemo(\n () => selectedProjects.filter((project) => !project.archived),\n [selectedProjects]\n );\n\n const unarchivableProjects = useMemo(\n () => selectedProjects.filter((project) => project.archived),\n [selectedProjects]\n );\n\n const allVisibleSelected =\n (data?.items.length ?? 0) > 0 &&\n (data?.items ?? []).every((project) => selectedProjectIds.has(project.id));\n const partiallySelected =\n (data?.items ?? []).some((project) => selectedProjectIds.has(project.id)) &&\n !allVisibleSelected;\n\n const {\n canCreateProject,\n canUpdateProject,\n canDeleteProject,\n canArchiveProject,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n // No client-side list gate: this page calls `myProjects` which is\n // membership-scoped — the resolver filters by actorId on the backend, so the\n // data set returned IS the access list. If the user is in zero projects, the\n // page renders the empty state below. Per-action checks (canCreateProject,\n // canDeleteProject, canArchiveProject) remain in place to hide buttons the\n // user isn't authorized for on their workspace.\n\n const handleCreateProject = () => {\n openCreateProjectDialog({} as Workspace);\n };\n\n const handleStatusFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.status;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ status: value as 'ACTIVE' | 'INACTIVE' | 'ARCHIVED' });\n }\n };\n\n const handleArchivedFilter = (value: string) => {\n if (value === 'all') {\n const nextFilters = { ...projectFilters };\n delete nextFilters.archived;\n setProjectFilters(nextFilters);\n } else {\n setProjectFilters({ archived: value === 'true' });\n }\n };\n\n const toggleTagFilter = (tag: ProjectTag) => {\n setActiveTagFilters((current) => {\n const exists = current.some((entry) => entry.tagId === tag.tagId);\n if (exists) {\n return current.filter((entry) => entry.tagId !== tag.tagId);\n }\n\n return [...current, tag];\n });\n };\n\n const clearAllProjectFilters = () => {\n setSearchValue('');\n setActiveTagFilters([]);\n setMatchAllTagFilters(true);\n clearProjectFilters();\n };\n\n const toggleAllProjects = (checked: boolean) => {\n (data?.items ?? []).forEach((project) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n });\n };\n\n const handleBulkArchive = async () => {\n if (archivableProjects.length === 0) return;\n await Promise.allSettled(archivableProjects.map((project) => archiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkUnarchive = async () => {\n if (unarchivableProjects.length === 0) return;\n await Promise.allSettled(unarchivableProjects.map((project) => unarchiveProject(project.id)));\n clearProjectSelection();\n };\n\n const handleBulkDelete = async () => {\n if (selectedProjects.length === 0) return;\n void nativeImpact('medium');\n const message =\n tWithFallback(t, 'projectsList.bulkDeleteConfirmPrefix', 'Delete ') +\n selectedProjects.length +\n ' ' +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmProject', 'selected project') +\n (selectedProjects.length === 1\n ? ''\n : tWithFallback(t, 'projectsList.bulkDeleteConfirmPluralSuffix', 's')) +\n tWithFallback(t, 'projectsList.bulkDeleteConfirmSuffix', '? This cannot be undone.');\n const native = await nativeConfirm({\n title: tWithFallback(t, 'projectsList.deleteProjectsTitle', 'Delete projects'),\n message,\n okButtonTitle: tWithFallback(t, 'common.delete', 'Delete'),\n cancelButtonTitle: tWithFallback(t, 'common.cancel', 'Cancel'),\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n await Promise.allSettled(selectedProjects.map((project) => deleteProject(project.id)));\n clearProjectSelection();\n };\n\n return (\n <PageContainer padded={false}>\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'projectsList.pageTitle', 'Projects')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'projectsList.pageSubtitle', 'Manage and organize your projects')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\">\n {/* View mode toggle */}\n <div className=\"flex items-center gap-1 border rounded-lg p-1\">\n <Button\n variant={viewMode === 'grid' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('grid')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.gridViewAriaLabel',\n 'Show projects in grid view'\n )}\n >\n <Grid3x3 size={16} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'secondary' : 'ghost'}\n size=\"sm\"\n onClick={() => setViewMode('list')}\n className=\"size-8 p-0\"\n aria-label={tWithFallback(\n t,\n 'projectsList.listViewAriaLabel',\n 'Show projects in list view'\n )}\n >\n <List size={16} />\n </Button>\n </div>\n {canCreateProject && (\n <Button onClick={handleCreateProject}>\n <Plus className=\"size-4 sm:mr-2\" />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </span>\n </Button>\n )}\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'projectsList.purpose',\n 'Projects are the individual streams of work inside your workspaces — a launch, a client engagement, a feature. Each carries its own members, tags and activity. Use search, status and tag filters to find the one you need, or create a new project to start tracking work.'\n )}\n </PagePurpose>\n\n {/* Filters & Search */}\n <div className=\"flex flex-col sm:flex-row gap-3\">\n <div className=\"relative flex-1\">\n <Search\n className=\"absolute left-3 top-1/2 transform -translate-y-1/2 text-text-secondary\"\n size={18}\n />\n <Input\n placeholder={tWithFallback(t, 'projectsList.searchPlaceholder', 'Search projects...')}\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n className=\"pl-[3.25rem]\"\n />\n </div>\n <Select value={projectFilters.status || 'all'} onValueChange={handleStatusFilter}>\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByStatus', 'Filter by status')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.statusAll', 'All Statuses')}\n </SelectItem>\n <SelectItem value=\"ACTIVE\">\n {tWithFallback(t, 'projectsList.statusActive', 'Active')}\n </SelectItem>\n <SelectItem value=\"INACTIVE\">\n {tWithFallback(t, 'projectsList.statusInactive', 'Inactive')}\n </SelectItem>\n </SelectContent>\n </Select>\n <Select\n value={\n projectFilters.archived === undefined\n ? 'all'\n : projectFilters.archived\n ? 'true'\n : 'false'\n }\n onValueChange={handleArchivedFilter}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.filterByArchive', 'Filter by archive')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.archiveAll', 'All Projects')}\n </SelectItem>\n <SelectItem value=\"false\">\n {tWithFallback(t, 'projectsList.archiveActiveProjects', 'Active Projects')}\n </SelectItem>\n <SelectItem value=\"true\">\n {tWithFallback(t, 'projectsList.archiveArchived', 'Archived')}\n </SelectItem>\n </SelectContent>\n </Select>\n {(projectFilters.search ||\n projectFilters.status ||\n projectFilters.archived !== undefined ||\n activeTagFilters.length > 0) && (\n <Button variant=\"outline\" onClick={clearAllProjectFilters}>\n {tWithFallback(t, 'projectsList.clearFilters', 'Clear Filters')}\n </Button>\n )}\n </div>\n\n {activeTagFilters.length > 0 && (\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface px-4 py-3\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <p className=\"text-sm font-medium\">\n {tWithFallback(t, 'projectsList.activeTagFilters', 'Active tag filters')}\n </p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(\n t,\n 'projectsList.activeTagFiltersDescription',\n 'Click a project tag to add or remove it from the current filter set.'\n )}\n </p>\n </div>\n <Select\n value={matchAllTagFilters ? 'all' : 'any'}\n onValueChange={(value) => setMatchAllTagFilters(value === 'all')}\n >\n <SelectTrigger className=\"w-full sm:w-[180px]\">\n <SelectValue\n placeholder={tWithFallback(t, 'projectsList.tagMatchMode', 'Tag match mode')}\n />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">\n {tWithFallback(t, 'projectsList.tagMatchAll', 'Match all tags')}\n </SelectItem>\n <SelectItem value=\"any\">\n {tWithFallback(t, 'projectsList.tagMatchAny', 'Match any tag')}\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"flex flex-wrap gap-2\">\n {activeTagFilters.map((tag) => (\n <TagBadge\n key={tag.tagId}\n id={tag.tagId}\n label={tag.label}\n color={tag.color ?? undefined}\n icon={tag.icon ?? undefined}\n description={tag.description ?? undefined}\n removable\n onRemove={() => toggleTagFilter(tag)}\n size=\"sm\"\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Error State */}\n {error && (\n <GlassCard treatment=\"plain\" className=\"border-status-error-border\">\n <CardContent className=\"pt-6\">\n <div className=\"flex flex-col items-center justify-center py-8 text-center\">\n <p className=\"text-status-error-text font-semibold mb-2\">\n {tWithFallback(t, 'projectsList.errorLoading', 'Error loading projects')}\n </p>\n <p className=\"text-sm text-text-secondary\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'projectsList.unknownError', 'An unknown error occurred')}\n </p>\n </div>\n </CardContent>\n </GlassCard>\n )}\n\n {/* Loading State */}\n {isLoading && (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <Card key={i} className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <div className=\"space-y-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-4 w-1/2\" />\n </div>\n </CardContent>\n </Card>\n ))}\n </div>\n )}\n\n {/* Empty State */}\n {!isLoading && !error && data?.items.length === 0 && (\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardContent className=\"pt-6\">\n <IllustratedEmptyState\n illustration={projectFilters.search ? 'empty-search' : 'empty-data'}\n title={tWithFallback(t, 'projectsList.noProjectsFound', 'No projects found')}\n description={\n projectFilters.search\n ? tWithFallback(\n t,\n 'projectsList.adjustSearchOrFilters',\n 'Try adjusting your search or filters'\n )\n : tWithFallback(\n t,\n 'projectsList.createFirstProject',\n 'Create your first project to get started organizing your work'\n )\n }\n action={\n !projectFilters.search && canCreateProject ? (\n <Button onClick={handleCreateProject}>\n <Plus size={16} className=\"mr-2\" />\n {tWithFallback(t, 'projectsList.createProject', 'Create Project')}\n </Button>\n ) : undefined\n }\n />\n </CardContent>\n </GlassCard>\n )}\n\n {/* Projects Grid */}\n {!isLoading && !error && data && data.items.length > 0 && (\n <>\n <div className=\"flex flex-col gap-3 rounded-lg border border-border-seam bg-bg-surface p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-text-primary\">\n <input\n type=\"checkbox\"\n checked={allVisibleSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelected && !allVisibleSelected;\n }\n }}\n onChange={(event) => toggleAllProjects(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'projectsList.selectAllAriaLabel',\n 'Select all projects'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(t, 'projectsList.selectAllVisible', 'Select all visible')}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedProjectIds.size} {tWithFallback(t, 'projectsList.selected', 'selected')}\n </span>\n {selectedProjectIds.size > 0 && (\n <Button variant=\"ghost\" size=\"sm\" onClick={clearProjectSelection}>\n {tWithFallback(t, 'projectsList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {/* Bulk mutations are bound to a single workspaceId via the\n archive/unarchive/delete hooks. On the cross-workspace\n /workspaces/projects route (workspaceId is undefined),\n hide them — selections can span workspaces and the bound\n hook would fail authorization for projects outside the\n caller's context. The per-card actions in ProjectCard\n still work because they resolve per-project. */}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkArchive}\n disabled={archivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.archiveSelected', 'Archive Selected')}\n {archivableProjects.length > 0 ? ` (${archivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canArchiveProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkUnarchive}\n disabled={unarchivableProjects.length === 0}\n >\n {tWithFallback(t, 'projectsList.unarchiveSelected', 'Unarchive Selected')}\n {unarchivableProjects.length > 0 ? ` (${unarchivableProjects.length})` : ''}\n </Button>\n )}\n {workspaceId && canDeleteProject && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkDelete}\n disabled={selectedProjects.length === 0}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'projectsList.deleteSelected', 'Delete Selected')}\n {selectedProjects.length > 0 ? ` (${selectedProjects.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n\n <div\n className={\n viewMode === 'list'\n ? 'space-y-3'\n : 'grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4'\n }\n >\n {data.items.map((project) => (\n <ProjectCard\n key={project.id}\n project={project}\n selected={selectedProjectIds.has(project.id)}\n canArchive={canArchiveProject}\n canDelete={canDeleteProject}\n canRename={canUpdateProject}\n onTagClick={toggleTagFilter}\n onToggleSelect={(checked) => {\n const isSelected = selectedProjectIds.has(project.id);\n if (checked !== isSelected) {\n toggleProjectSelection(project.id);\n }\n }}\n />\n ))}\n </div>\n\n {/* Pagination Info */}\n {data.total > data.items.length && (\n <div className=\"text-center text-sm text-text-secondary\">\n {tWithFallback(t, 'projectsList.showingPrefix', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'projectsList.showingOf', 'of')} {data.total}{' '}\n {tWithFallback(t, 'projectsList.showingProjects', 'projects')}\n </div>\n )}\n </>\n )}\n\n {/* Create Project Dialog */}\n {isCreateProjectDialogOpen && <CreateProjectDialog workspaceId={workspaceId} />}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceDetailPage.js","names":[],"sources":["../../src/pages/WorkspaceDetailPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams, useNavigate } from 'react-router';\nimport {\n Settings,\n ArrowLeft,\n FolderKanban,\n Users,\n Activity,\n ChevronRight,\n UserPlus,\n Archive,\n} from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n GlassCard,\n CardHeader,\n CardTitle,\n CardContent,\n Badge,\n Skeleton,\n CTAOverflowMenu,\n IllustratedEmptyState,\n PagePurpose,\n NextSteps,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { useWorkspaceActivity } from '../hooks/useWorkspaceActivity';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { useArchiveWorkspace } from '../hooks/useWorkspaceMutations';\n\nexport function WorkspaceDetailPage() {\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.detail', { workspaceId, requireParams: true });\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const [showArchiveDialog, setShowArchiveDialog] = useState(false);\n const [archiveError, setArchiveError] = useState<string | null>(null);\n const { mutate: archiveWorkspace, isPending: isArchiving } = useArchiveWorkspace();\n const { data: workspace, isLoading, error } = useWorkspace(workspaceId || '');\n const { data: projects } = useProjects({ workspaceId }, { limit: 1 }, workspaceId);\n const { data: members } = useWorkspaceMembers(workspaceId || '', { limit: 1 });\n const { data: activities } = useWorkspaceActivity(workspaceId);\n const {\n canViewWorkspace,\n canUpdateWorkspace,\n canArchiveWorkspace,\n canInviteMember,\n isLoading: permissionsLoading,\n } = useWorkspacePermissions();\n\n if (permissionsLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (!canViewWorkspace) {\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, 'workspaceDetail.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceDetail.accessDeniedDesc',\n \"You don't have permission to view this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (isLoading) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <Skeleton className=\"h-10 w-1/3\" />\n <div className=\"grid grid-cols-1 md:grid-cols-3 gap-4\">\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n <Skeleton className=\"h-32\" />\n </div>\n </div>\n );\n }\n\n if (error || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tWithFallback(t, 'workspaceDetail.notFound', 'Workspace not found')}\n description={tWithFallback(\n t,\n 'workspaceDetail.notFoundDescription',\n \"This workspace doesn't exist or you don't have permission to view it.\"\n )}\n />\n </div>\n );\n }\n\n const handleCardKeyDown = (e: React.KeyboardEvent, path: string) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n navigate(path);\n }\n };\n\n const handleArchive = () => {\n if (!workspaceId) return;\n setArchiveError(null);\n archiveWorkspace(workspaceId, {\n onSuccess: () => {\n setShowArchiveDialog(false);\n navigate(withCurrentSearch(`${basePath}/list`));\n },\n onError: () => {\n setArchiveError(\n tWithFallback(\n t,\n 'workspaceDetail.archiveFailed',\n 'The workspace could not be archived. Please try again.'\n )\n );\n },\n });\n };\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceDetail.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">{workspace.name}</h1>\n <div className=\"flex items-center gap-2 mt-2\">\n <Badge variant=\"outline\">{workspace.type}</Badge>\n <Badge>{workspace.status}</Badge>\n </div>\n </div>\n <CTAOverflowMenu\n primary={\n canUpdateWorkspace\n ? {\n label: tWithFallback(t, 'workspaceDetail.settings', 'Settings'),\n icon: <Settings size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/settings`)),\n }\n : canInviteMember\n ? {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={18} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n }\n : undefined\n }\n actions={[\n ...(canUpdateWorkspace && canInviteMember\n ? [\n {\n label: tWithFallback(t, 'workspaceDetail.inviteMembers', 'Invite Member'),\n icon: <UserPlus size={16} />,\n onSelect: () =>\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n },\n ]\n : []),\n ...(canArchiveWorkspace\n ? [\n {\n label: tWithFallback(\n t,\n 'workspaceDetail.archiveWorkspace',\n 'Archive Workspace'\n ),\n icon: <Archive size={16} />,\n onSelect: () => setShowArchiveDialog(true),\n },\n ]\n : []),\n ]}\n />\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'workspaceDetail.purpose',\n 'This is the home for a single workspace — the hub that ties together its projects, the people who collaborate in it, and everything that happens inside. Use the cards below to jump into work, manage who has access, or review recent activity.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"workspaces-workspace-detail\"\n steps={[\n {\n id: 'open-projects',\n label: tWithFallback(t, 'workspaceDetail.stepProjects', 'Open the projects board'),\n description: tWithFallback(\n t,\n 'workspaceDetail.stepProjectsDesc',\n 'Projects are where the actual work lives — start or pick one up.'\n ),\n icon: <FolderKanban size={16} />,\n onClick: () => navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`)),\n },\n ...(canInviteMember\n ? [\n {\n id: 'invite-member',\n label: tWithFallback(t, 'workspaceDetail.stepInvite', 'Invite a teammate'),\n description: tWithFallback(\n t,\n 'workspaceDetail.stepInviteDesc',\n 'Bring people into this workspace so they can collaborate.'\n ),\n icon: <UserPlus size={16} />,\n onClick: () => navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`)),\n },\n ]\n : []),\n {\n id: 'review-members',\n label: tWithFallback(t, 'workspaceDetail.stepMembers', 'Review who has access'),\n description: tWithFallback(\n t,\n 'workspaceDetail.stepMembersDesc',\n 'Check the current members and their roles.'\n ),\n icon: <Users size={16} />,\n onClick: () => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`)),\n },\n ...(canUpdateWorkspace\n ? [\n {\n id: 'configure-settings',\n label: tWithFallback(\n t,\n 'workspaceDetail.stepSettings',\n 'Configure workspace settings'\n ),\n description: tWithFallback(\n t,\n 'workspaceDetail.stepSettingsDesc',\n 'Set the name, domain and contact details for this workspace.'\n ),\n icon: <Settings size={16} />,\n onClick: () => navigate(withCurrentSearch(`${basePath}/${workspaceId}/settings`)),\n },\n ]\n : []),\n ]}\n />\n\n {/* Main + detail rail */}\n <div className=\"grid grid-cols-1 lg:grid-cols-3 gap-4 sm:gap-6\">\n {/* Main: stat metrics */}\n <div className=\"lg:col-span-2 space-y-4 sm:space-y-6\">\n <div className=\"grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <GlassCard\n treatment=\"metric\"\n glow\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/projects`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/projects`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <FolderKanban size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.projects', 'Projects')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {projects?.total ?? workspace.projectCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.projectsDesc', 'Projects in this workspace')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/members`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/members`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Users size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.members', 'Members')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">\n {members?.total ?? workspace.memberCount ?? 0}\n </p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.membersDesc', 'Workspace members')}\n </p>\n </CardContent>\n </GlassCard>\n\n <GlassCard\n treatment=\"metric\"\n className=\"cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-action-primary-bg/50\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}/activity`))}\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) =>\n handleCardKeyDown(e, withCurrentSearch(`${basePath}/${workspaceId}/activity`))\n }\n >\n <CardHeader className=\"flex flex-row items-center justify-between pb-2\">\n <CardTitle className=\"flex items-center gap-2 text-text-secondary text-sm font-medium\">\n <Activity size={18} className=\"text-text-muted\" />\n {tWithFallback(t, 'workspaceDetail.activity', 'Activity')}\n </CardTitle>\n <ChevronRight size={18} className=\"text-text-muted\" />\n </CardHeader>\n <CardContent>\n <p className=\"text-3xl font-bold tabular-nums\">{activities?.length ?? 0}</p>\n <p className=\"text-sm text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.activityDesc', 'Recent activity items')}\n </p>\n </CardContent>\n </GlassCard>\n </div>\n </div>\n\n {/* Detail rail: workspace info */}\n <aside className=\"lg:sticky lg:top-6 lg:self-start\">\n <GlassCard treatment=\"plain\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceDetail.workspaceInformation', 'Workspace Information')}\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-2\">\n <div className=\"grid grid-cols-2 gap-2 text-sm\">\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.id', 'ID:')}\n </span>\n <span className=\"font-mono text-xs break-all\">{workspace.id}</span>\n\n {workspace.domain && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.domain', 'Domain:')}\n </span>\n <span>{workspace.domain}</span>\n </>\n )}\n\n {workspace.contactEmail && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.contactEmail', 'Contact Email:')}\n </span>\n <span className=\"break-all\">{workspace.contactEmail}</span>\n </>\n )}\n\n {workspace.website && (\n <>\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.website', 'Website:')}\n </span>\n <a\n href={workspace.website}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"text-primary hover:underline transition-colors duration-200 break-all\"\n >\n {workspace.website}\n </a>\n </>\n )}\n\n <span className=\"text-text-muted\">\n {tWithFallback(t, 'workspaceDetail.created', 'Created:')}\n </span>\n <span>{new Date(workspace.createdAt).toLocaleDateString()}</span>\n </div>\n </CardContent>\n </GlassCard>\n </aside>\n </div>\n\n <AlertDialog\n open={showArchiveDialog}\n onOpenChange={(open) => {\n setShowArchiveDialog(open);\n if (!open) setArchiveError(null);\n }}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {tWithFallback(t, 'workspaceDetail.archiveTitle', 'Archive workspace?')}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {tWithFallback(\n t,\n 'workspaceDetail.archiveDescription',\n 'This removes the workspace from active work while preserving its data.'\n )}\n </AlertDialogDescription>\n </AlertDialogHeader>\n {archiveError && <p className=\"text-sm text-status-error-text\">{archiveError}</p>}\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isArchiving}>\n {tWithFallback(t, 'common.cancel', 'Cancel')}\n </AlertDialogCancel>\n <AlertDialogAction\n disabled={isArchiving}\n onClick={(event) => {\n event.preventDefault();\n handleArchive();\n }}\n >\n {isArchiving\n ? tWithFallback(t, 'workspaceDetail.archiving', 'Archiving...')\n : tWithFallback(t, 'workspaceDetail.archive', 'Archive')}\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceInvitePage.js","names":[],"sources":["../../src/pages/WorkspaceInvitePage.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router';\nimport {\n AlertCircle,\n ArrowLeft,\n CheckCircle2,\n Download,\n Loader2,\n Mail,\n RefreshCw,\n Send,\n Upload,\n UserPlus,\n XCircle,\n} from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n GlassCard,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n Skeleton,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n ResponsiveTable,\n PagePurpose,\n NextSteps,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport type { ResponsiveTableColumn } from '@burdenoff/fe-libs/ui';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { getProjectDisplayNames, getRoleDisplayNames } from '../utils/invitationDisplay';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspaceRoles } from '../hooks/useWorkspaceRoles';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceInvitations } from '../hooks/useWorkspaceMembers';\nimport {\n useBulkCreateInvitations,\n useCancelInvitation,\n useCreateInvitation,\n useResendInvitation,\n} from '../hooks/useMemberMutations';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport type { Project, WorkspaceInvitation } from '../types';\nimport {\n downloadWorkspaceBulkInviteTemplate,\n parseWorkspaceBulkInviteCsv,\n type WorkspaceBulkInviteRow,\n} from '../utils/workspaceBulkInviteCsv';\n\ntype InviteMode = 'single' | 'bulk';\n\ntype RowStatus =\n | { kind: 'idle' }\n | { kind: 'sending' }\n | { kind: 'invited' }\n | { kind: 'failed'; message: string };\n\ninterface PreviewRow extends WorkspaceBulkInviteRow {\n status: RowStatus;\n}\n\ntype FeedbackState = {\n tone: 'success' | 'error';\n message: string;\n} | null;\n\nconst MAX_BULK_INVITE_FILE_SIZE = 1024 * 1024;\nconst MAX_BULK_INVITE_ROWS = 500;\n\nconst previewColumns: ResponsiveTableColumn<PreviewRow>[] = [\n {\n key: 'rowNumber',\n header: '#',\n priority: 'secondary',\n headerClassName: 'text-xs font-semibold uppercase tracking-wide text-text-secondary',\n cellClassName: 'text-xs text-text-secondary tabular-nums',\n cell: (row) => row.rowNumber,\n },\n {\n key: 'email',\n header: 'Email',\n priority: 'primary',\n headerClassName: 'text-xs font-semibold uppercase tracking-wide text-text-secondary',\n cellClassName: 'text-sm',\n cell: (row) => row.email || <span className=\"italic text-text-secondary\">(empty)</span>,\n },\n {\n key: 'status',\n header: 'Status',\n priority: 'secondary',\n headerClassName: 'text-xs font-semibold uppercase tracking-wide text-text-secondary',\n cellClassName: 'text-xs',\n cell: (row) => {\n const hasParseErrors = row.errors.length > 0;\n if (hasParseErrors) {\n return (\n <div className=\"space-y-1 text-status-error-text\">\n {row.errors.map((errorText) => (\n <span\n key={`${row.rowNumber}-${errorText}`}\n className=\"inline-flex items-center gap-1 pr-3\"\n >\n <XCircle size={14} />\n {errorText}\n </span>\n ))}\n </div>\n );\n }\n if (row.status.kind === 'sending') {\n return (\n <span className=\"inline-flex items-center gap-1 text-text-secondary\">\n <Loader2 size={14} className=\"animate-spin\" />\n Sending...\n </span>\n );\n }\n if (row.status.kind === 'invited') {\n return (\n <span className=\"inline-flex items-center gap-1 text-green-700 dark:text-green-400\">\n <CheckCircle2 size={14} />\n Invited\n </span>\n );\n }\n if (row.status.kind === 'failed') {\n return (\n <span className=\"inline-flex items-center gap-1 text-status-error-text\">\n <XCircle size={14} />\n {row.status.message}\n </span>\n );\n }\n return <span className=\"text-text-secondary\">Ready</span>;\n },\n },\n];\n\nfunction isCsvFile(file: File): boolean {\n return file.type === 'text/csv' || file.name.toLowerCase().endsWith('.csv');\n}\n\nfunction formatDate(value?: string) {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction formatInviteError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('CONFLICT')) {\n return 'An invite already exists for this email address.';\n }\n if (message.includes('already a member')) {\n return 'This user is already a member of the workspace.';\n }\n return 'Something went wrong. Please try again or contact support.';\n}\n\nfunction formatSelectionSummary(\n items: string[],\n emptyLabel: string,\n formatter?: (value: string) => string\n) {\n if (items.length === 0) {\n return emptyLabel;\n }\n\n return items.map((item) => (formatter ? formatter(item) : item)).join(', ');\n}\n\nfunction buildInvitationPayload(\n workspaceId: string,\n email: string,\n roleIds: string[],\n projectIds: string[]\n) {\n return {\n email,\n workspaceId,\n roleIds,\n ...(projectIds.length > 0\n ? {\n context: {\n type: 'workspace.invitation',\n metadata: { projectIds },\n },\n }\n : {}),\n };\n}\n\nfunction WorkspaceSelectorSection({\n selectedRoleIds,\n onRoleToggle,\n selectedProjectIds,\n onProjectToggle,\n availableRoles,\n availableProjects,\n isLoadingRoles,\n isLoadingProjects,\n disabled,\n}: {\n selectedRoleIds: string[];\n onRoleToggle: (roleId: string, checked: boolean) => void;\n selectedProjectIds: string[];\n onProjectToggle: (projectId: string, checked: boolean) => void;\n availableRoles: Array<{\n id: string;\n name: string;\n description?: string | null;\n isSystem: boolean;\n }>;\n availableProjects: Project[];\n isLoadingRoles: boolean;\n isLoadingProjects: boolean;\n disabled: boolean;\n}) {\n return (\n <div className=\"space-y-6\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">Roles (Optional)</h3>\n <p className=\"text-sm text-text-secondary\">\n Choose roles. Hover any role to preview its full description.\n </p>\n </div>\n {selectedRoleIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedRoleIds.length} selected</Badge>\n ) : null}\n </div>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-text-secondary\">Loading roles…</p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No assignable roles were returned for this workspace.\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = selectedRoleIds.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onRoleToggle(role.id, Boolean(checked))}\n aria-label={`Select ${role.name}`}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-text-secondary\">\n {role.description || 'No description available for this role.'}\n </p>\n {role.isSystem ? <p className=\"mt-2 text-xs\">System role</p> : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">Projects to add on acceptance (Optional)</h3>\n <p className=\"text-sm text-text-secondary\">\n The invited user will be added to these projects automatically after they accept.\n </p>\n </div>\n {selectedProjectIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedProjectIds.length} selected</Badge>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-text-secondary\">Loading projects…</p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No active projects are available in this workspace yet.\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = selectedProjectIds.includes(project.id);\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">{project.name}</p>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onProjectToggle(project.id, Boolean(checked))}\n aria-label={`Select ${project.name}`}\n />\n </label>\n );\n })\n )}\n </div>\n </div>\n </div>\n );\n}\n\nfunction WorkspaceInvitePageContent() {\n const { workspaceId: routeWorkspaceId } = useParams<{ workspaceId: string }>();\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/workspaces', workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const workspaceId = routeWorkspaceId || contextWorkspaceId || '';\n\n useWorkspacesPageView('workspaces.invite', { workspaceId: workspaceId || undefined });\n\n const { canInviteMember, isLoading: permissionsLoading } = useWorkspacePermissions();\n const {\n data: workspace,\n isLoading: isLoadingWorkspace,\n error: workspaceError,\n } = useWorkspace(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const {\n data: invitationsData,\n isLoading: isLoadingInvitations,\n isFetching: isFetchingInvitations,\n error: invitationsError,\n refetch: refetchInvitations,\n } = useWorkspaceInvitations(workspaceId, 'PENDING', { limit: 50 }, { retry: false });\n const availableProjects = useMemo(\n () => (projectList?.items ?? []).filter((project) => !project.archived),\n [projectList?.items]\n );\n\n const [mode, setMode] = useState<InviteMode>('single');\n const [email, setEmail] = useState('');\n const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);\n const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);\n const [singleError, setSingleError] = useState<string | null>(null);\n const [singleSuccess, setSingleSuccess] = useState<string | null>(null);\n const [bulkError, setBulkError] = useState<string | null>(null);\n const [bulkSuccess, setBulkSuccess] = useState<string | null>(null);\n const [pendingFeedback, setPendingFeedback] = useState<FeedbackState>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [dragActive, setDragActive] = useState(false);\n const [isSubmittingBulk, setIsSubmittingBulk] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [cancelTarget, setCancelTarget] = useState<WorkspaceInvitation | null>(null);\n\n const { mutateAsync: createInvitation, isPending: isCreatingInvitation } =\n useCreateInvitation(workspaceId);\n const { mutateAsync: bulkCreateInvitations, isPending: isCreatingBulkInvites } =\n useBulkCreateInvitations();\n const { mutate: resendInvitation, isPending: isResendingInvitation } =\n useResendInvitation(workspaceId);\n const { mutate: cancelInvitation, isPending: isCancellingInvitation } =\n useCancelInvitation(workspaceId);\n const [invitationActionId, setInvitationActionId] = useState<string | null>(null);\n\n const pendingInvitations = invitationsData?.items ?? [];\n const isMutatingInvite = isCreatingInvitation || isCreatingBulkInvites || isSubmittingBulk;\n const selectedRoleNames = useMemo(\n () =>\n availableRoles.filter((role) => selectedRoleIds.includes(role.id)).map((role) => role.name),\n [availableRoles, selectedRoleIds]\n );\n const selectedProjectNames = useMemo(\n () =>\n availableProjects\n .filter((project) => selectedProjectIds.includes(project.id))\n .map((project) => project.name),\n [availableProjects, selectedProjectIds]\n );\n\n const readyRows = useMemo(\n () => rows.filter((row) => row.errors.length === 0 && row.status.kind !== 'invited'),\n [rows]\n );\n const invalidRows = useMemo(() => rows.filter((row) => row.errors.length > 0), [rows]);\n\n if (!workspaceId) {\n return (\n <div className=\"space-y-4 p-6\">\n <div className=\"rounded-xl border bg-bg-surface p-8 text-center\">\n <h1 className=\"text-2xl font-bold\">\n {tWithFallback(t, 'workspaceInvite.noWorkspaceTitle', 'Select a workspace first')}\n </h1>\n <p className=\"mt-2 text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.noWorkspaceDescription',\n 'Choose a current workspace from the header or open a workspace detail page before inviting members.'\n )}\n </p>\n <div className=\"mt-4\">\n <Button\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n variant=\"outline\"\n >\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n if (permissionsLoading || isLoadingWorkspace) {\n return (\n <div className=\"space-y-6 p-6\">\n <Skeleton className=\"h-10 w-48\" />\n <Skeleton className=\"h-40 w-full\" />\n <Skeleton className=\"h-80 w-full\" />\n </div>\n );\n }\n\n if (!canInviteMember) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'workspaceInvite.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.accessDeniedDescription',\n \"You don't have permission to invite members into this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (workspaceError || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <div className=\"rounded-xl border bg-bg-surface p-8 text-center text-status-error-text\">\n {tWithFallback(\n t,\n 'workspaceInvite.workspaceLoadError',\n 'Unable to load workspace invite details.'\n )}\n </div>\n </div>\n );\n }\n\n const handleRoleToggle = (roleId: string, checked: boolean) => {\n setSelectedRoleIds((prev) =>\n checked\n ? [...prev, roleId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== roleId)\n );\n };\n\n const handleProjectToggle = (projectId: string, checked: boolean) => {\n setSelectedProjectIds((prev) =>\n checked\n ? [...prev, projectId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== projectId)\n );\n };\n\n const handleSingleInvite = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSingleError(null);\n setSingleSuccess(null);\n setPendingFeedback(null);\n\n if (!email.trim()) {\n setSingleError('Email is required.');\n return;\n }\n\n try {\n await createInvitation(\n buildInvitationPayload(\n workspaceId,\n email.trim().toLowerCase(),\n selectedRoleIds,\n selectedProjectIds\n )\n );\n setEmail('');\n setSingleSuccess(`Invitation sent to ${email.trim().toLowerCase()}.`);\n } catch (error) {\n setSingleError(formatInviteError(error));\n }\n };\n\n const handleFile = (file: File) => {\n if (!isCsvFile(file)) {\n setBulkError('Please upload a .csv file.');\n return;\n }\n\n if (file.size > MAX_BULK_INVITE_FILE_SIZE) {\n setBulkError('CSV file is too large (max 1 MB).');\n return;\n }\n\n const reader = new FileReader();\n reader.onload = () => {\n const text = typeof reader.result === 'string' ? reader.result : '';\n const parsed = parseWorkspaceBulkInviteCsv(text);\n const limitedRows = parsed.slice(0, MAX_BULK_INVITE_ROWS);\n setRows(limitedRows.map((row) => ({ ...row, status: { kind: 'idle' } })));\n setFileName(file.name);\n setBulkError(\n parsed.length > MAX_BULK_INVITE_ROWS\n ? `Only the first ${MAX_BULK_INVITE_ROWS} rows were loaded. Split larger CSV files into smaller batches.`\n : null\n );\n setBulkSuccess(null);\n setPendingFeedback(null);\n };\n reader.onerror = () => {\n setBulkError('Failed to read CSV file.');\n };\n reader.readAsText(file);\n };\n\n const handleBulkInvite = async () => {\n if (readyRows.length === 0 || isSubmittingBulk) {\n return;\n }\n\n setBulkError(null);\n setBulkSuccess(null);\n setPendingFeedback(null);\n setIsSubmittingBulk(true);\n setRows((prev) =>\n prev.map((row) =>\n row.errors.length === 0 && row.status.kind !== 'invited'\n ? { ...row, status: { kind: 'sending' } }\n : row\n )\n );\n\n try {\n await bulkCreateInvitations(\n readyRows.map((row) =>\n buildInvitationPayload(workspaceId, row.email, selectedRoleIds, selectedProjectIds)\n )\n );\n\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'invited' } } : row\n )\n );\n setBulkSuccess(\n `Sent ${readyRows.length} workspace invitation${readyRows.length === 1 ? '' : 's'}.`\n );\n } catch (error) {\n const message = formatInviteError(error);\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'failed', message } } : row\n )\n );\n setBulkError(message);\n } finally {\n setIsSubmittingBulk(false);\n }\n };\n\n const handleResendInvitation = (invitation: WorkspaceInvitation) => {\n setPendingFeedback(null);\n setInvitationActionId(invitation.id);\n resendInvitation(invitation.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation resent to ${invitation.email}.`,\n });\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n const handleConfirmCancel = () => {\n if (!cancelTarget) {\n return;\n }\n\n setPendingFeedback(null);\n setInvitationActionId(cancelTarget.id);\n cancelInvitation(cancelTarget.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation cancelled for ${cancelTarget.email}.`,\n });\n setCancelTarget(null);\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n return (\n <>\n <AlertDialog\n open={Boolean(cancelTarget)}\n onOpenChange={(open) => !open && setCancelTarget(null)}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Cancel pending invitation?</AlertDialogTitle>\n <AlertDialogDescription>\n {cancelTarget\n ? `This will revoke the pending invite for ${cancelTarget.email}. They will need a new invitation to join later.`\n : 'This will revoke the pending invitation.'}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isCancellingInvitation}>Keep invitation</AlertDialogCancel>\n <AlertDialogAction onClick={handleConfirmCancel} disabled={isCancellingInvitation}>\n {isCancellingInvitation ? <Loader2 className=\"size-4 animate-spin\" /> : null}\n Cancel invitation\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n <div className=\"space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}`))}\n className=\"-ml-2 mb-2 gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspace', 'Back to Workspace')}\n </Button>\n <div className=\"flex items-center gap-2\">\n <UserPlus className=\"text-primary\" size={24} />\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'workspaceInvite.title', 'Invite Members')}\n </h1>\n </div>\n <p className=\"mt-2 text-sm text-text-secondary sm:text-base\">\n {workspace.name} —{' '}\n {tWithFallback(\n t,\n 'workspaceInvite.subtitle',\n 'Invite people into this workspace and optionally grant project access.'\n )}\n </p>\n </div>\n </div>\n\n <PagePurpose>\n {tWithFallback(\n t,\n 'workspaceInvite.purpose',\n 'Use this page to bring new people into the workspace. First choose the roles and projects they should get the moment they accept, then send a single invitation by email or upload a CSV to invite many people at once. Invitations you have already sent appear at the bottom, where you can resend or cancel them.'\n )}\n </PagePurpose>\n\n <NextSteps\n storageKey=\"workspaces-workspace-invite\"\n steps={[\n {\n id: 'pick-access',\n label: tWithFallback(\n t,\n 'workspaceInvite.stepAccess',\n 'Pick the roles and projects to grant'\n ),\n description: tWithFallback(\n t,\n 'workspaceInvite.stepAccessDesc',\n 'These are applied automatically when each person accepts.'\n ),\n icon: <UserPlus size={16} />,\n onClick: () =>\n document\n .getElementById('invite-access-section')\n ?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n {\n id: 'send-invite',\n label: tWithFallback(t, 'workspaceInvite.stepSend', 'Send the invitation'),\n description: tWithFallback(\n t,\n 'workspaceInvite.stepSendDesc',\n 'Enter one email, or switch to the Bulk CSV tab to invite many at once.'\n ),\n icon: <Send size={16} />,\n onClick: () =>\n document\n .getElementById('invite-send-section')\n ?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n {\n id: 'review-pending',\n label: tWithFallback(\n t,\n 'workspaceInvite.stepPending',\n 'Track your pending invitations'\n ),\n description: tWithFallback(\n t,\n 'workspaceInvite.stepPendingDesc',\n 'Resend or cancel invitations that have not been accepted yet.'\n ),\n icon: <Mail size={16} />,\n onClick: () =>\n document\n .getElementById('invite-pending-section')\n ?.scrollIntoView({ behavior: 'smooth', block: 'start' }),\n },\n ]}\n />\n\n <GlassCard treatment=\"plain\" id=\"invite-access-section\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceInvite.setupTitle', 'Access to grant on acceptance')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <WorkspaceSelectorSection\n selectedRoleIds={selectedRoleIds}\n onRoleToggle={handleRoleToggle}\n selectedProjectIds={selectedProjectIds}\n onProjectToggle={handleProjectToggle}\n availableRoles={availableRoles}\n availableProjects={availableProjects}\n isLoadingRoles={isLoadingRoles}\n isLoadingProjects={isLoadingProjects}\n disabled={isMutatingInvite}\n />\n </CardContent>\n </GlassCard>\n\n {/* The one emphasized zone: live selection summary */}\n <GlassCard treatment=\"metric\" glow className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>Selection summary</CardTitle>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2 rounded-lg border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">Roles</p>\n <Badge variant=\"secondary\" className=\"tabular-nums\">\n {selectedRoleNames.length}\n </Badge>\n </div>\n <p className=\"text-sm text-text-muted\">\n {formatSelectionSummary(selectedRoleNames, 'No roles selected')}\n </p>\n </div>\n <div className=\"space-y-2 rounded-lg border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">Projects</p>\n <Badge variant=\"secondary\" className=\"tabular-nums\">\n {selectedProjectNames.length}\n </Badge>\n </div>\n <p className=\"text-sm text-text-muted\">\n {formatSelectionSummary(\n selectedProjectNames,\n 'No project access will be granted automatically'\n )}\n </p>\n </div>\n </CardContent>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" id=\"invite-send-section\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceInvite.sendTitle', 'Send invitations')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Tabs\n value={mode}\n onValueChange={(value) => setMode(value as InviteMode)}\n className=\"space-y-5\"\n >\n <TabsList>\n <TabsTrigger value=\"single\">\n {tWithFallback(t, 'workspaceInvite.modeSingle', 'Single')}\n </TabsTrigger>\n <TabsTrigger value=\"bulk\">\n {tWithFallback(t, 'workspaceInvite.modeBulk', 'Bulk CSV')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"single\" className=\"space-y-4\">\n {singleError ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{singleError}</span>\n </div>\n ) : null}\n {singleSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{singleSuccess}</span>\n </div>\n ) : null}\n\n <form onSubmit={handleSingleInvite} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-invite-email\" className=\"text-sm font-medium\">\n {tWithFallback(t, 'workspaceInvite.emailLabel', 'Email Address')}\n </label>\n <Input\n id=\"workspace-invite-email\"\n type=\"email\"\n placeholder={tWithFallback(\n t,\n 'workspaceInvite.emailPlaceholder',\n 'member@example.com'\n )}\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n disabled={isCreatingInvitation}\n />\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={isCreatingInvitation} className=\"gap-2\">\n {isCreatingInvitation ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Send className=\"size-4\" />\n )}\n {tWithFallback(t, 'workspaceInvite.sendSingleButton', 'Send Invitation')}\n </Button>\n </div>\n </form>\n </TabsContent>\n\n <TabsContent value=\"bulk\" className=\"space-y-5\">\n {bulkError ? (\n <div className=\"flex items-center gap-2 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{bulkError}</span>\n </div>\n ) : null}\n {bulkSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{bulkSuccess}</span>\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-1\">\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.bulkDescription',\n 'Upload a CSV with one email column. All valid rows will receive the currently selected roles and projects.'\n )}\n </p>\n <div className=\"rounded-md border bg-bg-sunken/30 px-3 py-2 text-xs text-text-secondary\">\n <p className=\"font-medium text-text-primary\">Sample format</p>\n <pre className=\"mt-1 whitespace-pre-wrap\">\n email{`\\n`}alice@example.com{`\\n`}bob@example.com\n </pre>\n </div>\n {(projectList?.total ?? 0) > availableProjects.length ? (\n <p className=\"text-xs text-text-secondary\">\n Showing the first {availableProjects.length} active projects. Narrow the\n workspace project list if you need to invite into more.\n </p>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={downloadWorkspaceBulkInviteTemplate}\n className=\"gap-2\"\n >\n <Download size={16} />\n {tWithFallback(t, 'workspaceInvite.downloadTemplate', 'Download template')}\n </Button>\n </div>\n\n <div\n onDragOver={(event) => {\n event.preventDefault();\n setDragActive(true);\n }}\n onDragLeave={() => setDragActive(false)}\n onDrop={(event) => {\n event.preventDefault();\n setDragActive(false);\n const file = event.dataTransfer.files?.[0];\n if (file) {\n handleFile(file);\n } else {\n setBulkError('Please upload a .csv file.');\n }\n }}\n className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 transition-colors ${\n dragActive\n ? 'border-primary bg-action-primary-bg/5'\n : 'border-border-subtle bg-bg-surface'\n }`}\n >\n <Upload size={28} className=\"text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-sm font-medium\">\n {fileName || 'Drop a CSV file here or click to browse'}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n CSV files only. Required column: email.\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) => {\n const file = event.target.files?.[0];\n if (file) {\n handleFile(file);\n }\n event.target.value = '';\n }}\n className=\"hidden\"\n aria-label={tWithFallback(\n t,\n 'workspaceInvite.chooseCsvFileAriaLabel',\n 'Choose CSV file'\n )}\n />\n <Button type=\"button\" onClick={() => fileInputRef.current?.click()}>\n {tWithFallback(t, 'workspaceInvite.chooseFile', 'Choose file')}\n </Button>\n </div>\n\n {rows.length > 0 ? (\n <>\n <div className=\"flex flex-wrap gap-4 text-xs text-text-secondary\">\n <span>Total rows: {rows.length}</span>\n <span>Ready: {readyRows.length}</span>\n <span>With errors: {invalidRows.length}</span>\n {invalidRows.length > 0 ? (\n <Badge variant=\"destructive\">Invalid rows will be skipped</Badge>\n ) : null}\n </div>\n {invalidRows.length > 0 ? (\n <div className=\"rounded-md border border-status-error-border/20 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n {invalidRows.length} row{invalidRows.length === 1 ? '' : 's'} need\n attention. Only valid rows can be invited.\n </div>\n ) : null}\n <div className=\"overflow-hidden rounded-xl border\">\n <ResponsiveTable<PreviewRow>\n columns={previewColumns}\n data={rows}\n rowKey={(row) => `${row.rowNumber}-${row.email}`}\n />\n </div>\n </>\n ) : (\n <div className=\"rounded-xl border border-dashed p-6 text-center text-sm text-text-secondary\">\n Upload a CSV to preview invite rows here before sending.\n </div>\n )}\n\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n onClick={handleBulkInvite}\n disabled={readyRows.length === 0 || isCreatingBulkInvites || isSubmittingBulk}\n className=\"gap-2\"\n >\n {isCreatingBulkInvites || isSubmittingBulk ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Send className=\"size-4\" />\n )}\n {tWithFallback(t, 'workspaceInvite.sendBulkButton', 'Send Bulk Invites')}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n </CardContent>\n </GlassCard>\n\n <GlassCard treatment=\"plain\" id=\"invite-pending-section\" className=\"border-border-seam\">\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Mail className=\"size-5 text-primary\" />\n {tWithFallback(t, 'workspaceInvite.pendingTitle', 'Pending Invitations')}\n <Badge variant=\"secondary\" className=\"tabular-nums\">\n {pendingInvitations.length}\n </Badge>\n </CardTitle>\n </CardHeader>\n <CardContent>\n {pendingFeedback ? (\n <div\n className={`mb-4 flex items-center gap-2 rounded-md border p-3 text-sm ${\n pendingFeedback.tone === 'success'\n ? 'border-green-500/20 bg-green-500/10 text-green-700 dark:text-green-400'\n : 'border-status-error-border/20 bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {pendingFeedback.tone === 'success' ? (\n <CheckCircle2 className=\"size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"size-4 shrink-0\" />\n )}\n <span>{pendingFeedback.message}</span>\n </div>\n ) : null}\n {isLoadingInvitations ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n </div>\n ) : invitationsError && pendingInvitations.length === 0 ? (\n <div\n role=\"alert\"\n className=\"space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\"\n >\n <p>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingLoadFailed',\n 'Failed to load pending invitations for this workspace.'\n )}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : pendingInvitations.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-notifications\"\n title={tWithFallback(\n t,\n 'workspaceInvite.pendingEmptyTitle',\n 'No pending invitations'\n )}\n description={tWithFallback(\n t,\n 'workspaceInvite.pendingEmpty',\n 'Invitations you send for this workspace will show up here until they are accepted, so you can resend or cancel them.'\n )}\n />\n ) : (\n <div className=\"space-y-3\">\n {invitationsError ? (\n <div\n role=\"alert\"\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\"\n >\n <span>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingStaleWarning',\n 'Pending invitations may be outdated. Try again to refresh this list.'\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : null}\n {pendingInvitations.map((invitation) => {\n const roleNames = getRoleDisplayNames(invitation);\n const projectNames = getProjectDisplayNames(invitation);\n const isRunningAction =\n invitationActionId === invitation.id &&\n (isResendingInvitation || isCancellingInvitation);\n\n return (\n <div key={invitation.id} className=\"rounded-lg border p-4\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-semibold\">{invitation.email}</p>\n <Badge variant=\"outline\">{invitation.status}</Badge>\n </div>\n <div className=\"grid gap-3 text-sm text-text-secondary sm:grid-cols-2\">\n <div>\n <p className=\"font-medium text-text-primary\">Received</p>\n <p>{formatDate(invitation.createdAt) ?? '—'}</p>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">Expires</p>\n <p>{formatDate(invitation.expiresAt) ?? '—'}</p>\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Roles</p>\n <div className=\"flex flex-wrap gap-2\">\n {roleNames.length > 0 ? (\n roleNames.map((roleName, index) => (\n <Badge\n key={`${invitation.id}-${invitation.roleIds[index] ?? roleName}`}\n variant=\"outline\"\n >\n {roleName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n No roles selected\n </span>\n )}\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Projects</p>\n <div className=\"flex flex-wrap gap-2\">\n {projectNames.length > 0 ? (\n projectNames.map((projectName, index) => (\n <Badge\n key={`${invitation.id}-project-${projectName}-${index}`}\n variant=\"secondary\"\n >\n {projectName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n No project access will be granted automatically\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => handleResendInvitation(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isResendingInvitation ? (\n <RefreshCw className=\"size-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"size-4\" />\n )}\n Resend\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => setCancelTarget(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isCancellingInvitation ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <XCircle className=\"size-4\" />\n )}\n Cancel\n </Button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </CardContent>\n </GlassCard>\n </div>\n </>\n );\n}\n\nexport function WorkspaceInvitePage() {\n const { workspaceId: routeWorkspaceId } = useParams<{ workspaceId: string }>();\n const { workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const permissionScopeId = routeWorkspaceId || contextWorkspaceId || null;\n\n return (\n <PermissionProvider scopeId={permissionScopeId} gateway=\"workspace\" debug={false}>\n <WorkspaceInvitePageContent />\n </PermissionProvider>\n );\n}\n"],"mappings":"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"}
|