@burdenoff/microfe-workspaces 2026.625.3 → 2026.625.5
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/hooks/useMemberMutations.js +6 -6
- package/dist/hooks/useMemberMutations.js.map +1 -1
- package/dist/pages/ActivityListPage.js +32 -48
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/MembersListPage.js +196 -241
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +69 -78
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +37 -52
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +73 -86
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +103 -118
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +74 -76
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +167 -178
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js +21 -22
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +26 -27
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +19 -23
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js +30 -32
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +18 -20
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js +18 -19
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +14 -12
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +20 -21
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MembersListPage.js","names":[],"sources":["../../src/pages/MembersListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useNavigate, useParams, useSearchParams } from 'react-router-dom';\nimport { Users, UserPlus, Search, AlertCircle, RefreshCw } from 'lucide-react';\nimport { Button, Input, Skeleton, Badge, PageContainer } from '@burdenoff/fe-libs/ui';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { useActorProfiles, type ActorInput, type ActorProfileMap } from '@burdenoff/fe-libs/shared';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useWorkspaceMembers, useWorkspaceInvitations } from '../hooks/useWorkspaceMembers';\nimport { useRemoveUserFromWorkspace, useResendInvitation } from '../hooks/useMemberMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { MemberListItem } from '../components/MemberListItem';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\n\nexport function MembersListPage() {\n const { t } = useI18n();\n\n const {\n memberFilters,\n setMemberFilters,\n selectedMemberIds,\n toggleMemberSelection,\n clearMemberSelection,\n } = useWorkspacesStore();\n const navigate = useNavigate();\n const { workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { workspaceId: urlWorkspaceId } = useParams<{ workspaceId: string }>();\n\n // Prefer URL workspaceId over context (user navigated to specific workspace).\n // WorkspaceContextPathSync keeps this URL segment in step with the active context\n // on a context switch, so the override can't point at a stale workspace.\n // (BOFF-2805 follow-up)\n const workspaceId = urlWorkspaceId || contextWorkspaceId;\n\n useWorkspacesPageView('workspaces.members', { workspaceId, requireParams: true });\n\n const [searchValue, setSearchValue] = useState(memberFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n setMemberFilters({ search: debouncedSearch });\n }, [debouncedSearch, setMemberFilters]);\n\n // Fetch members and invitations\n const {\n data: membersData,\n isLoading: isLoadingMembers,\n error: membersError,\n } = useWorkspaceMembers(workspaceId || '');\n\n const { data: invitationsData, isLoading: isLoadingInvitations } = useWorkspaceInvitations(\n workspaceId || '',\n 'PENDING'\n );\n\n const { mutate: resendInvitation, isPending: isResending } = useResendInvitation();\n const { mutate: removeUserFromWorkspace, isPending: isRemovingMembers } =\n useRemoveUserFromWorkspace();\n const [resendingId, setResendingId] = useState<string | null>(null);\n const [selectedInvitationIds, setSelectedInvitationIds] = useState<Set<string>>(new Set());\n\n const handleResendInvitation = (invitationId: string) => {\n setResendingId(invitationId);\n resendInvitation(invitationId, {\n onSuccess: () => {\n setResendingId(null);\n },\n onError: (error) => {\n console.error('Failed to resend invitation:', error);\n setResendingId(null);\n },\n });\n };\n\n const members = useMemo(() => membersData?.items || [], [membersData?.items]);\n const pendingInvitations = useMemo(() => invitationsData?.items || [], [invitationsData?.items]);\n const isLoading = isLoadingMembers || isLoadingInvitations;\n const hasMembers = members.length > 0;\n const hasPendingInvitations = pendingInvitations.length > 0;\n\n // Resolve every member's userId to displayName + email via the shared\n // ActorProfile cache so search can match human-friendly fields instead of\n // raw UUIDs.\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n const memberActors = useMemo<ActorInput[]>(\n () => members.map((m) => ({ actorId: m.userId, actorType: 'user' })),\n [members]\n );\n const profileMap: ActorProfileMap = useActorProfiles(memberActors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n // Filter members by resolved identity (displayName, email) — fall back to\n // raw userId only if the profile hasn't resolved yet so search still works\n // during the brief loading window.\n const filteredMembers = members.filter((member) => {\n if (!debouncedSearch) return true;\n const needle = debouncedSearch.toLowerCase();\n\n // Primary: the shared ActorProfile cache — canonical, works for every\n // member regardless of how they joined.\n const profile = profileMap[member.userId];\n if (profile) {\n if (profile.displayName.toLowerCase().includes(needle)) return true;\n if (profile.email?.toLowerCase().includes(needle)) return true;\n }\n\n // Fallback: the denormalized identity surfaced by the backend through\n // `WorkspaceMember.context.metadata` (populated from\n // `WorkspaceMember.email` for members who joined via invitation — PR #66).\n // Available synchronously, so search still matches during the brief window\n // before the ActorProfile lookup resolves.\n //\n // The metadata field is typed `Record<string, unknown>`, so we MUST guard\n // every read with `typeof === 'string'`. A non-string value (e.g. a number,\n // null, or a nested object accidentally surfaced by a future backend\n // mapper) would otherwise crash the page on `.toLowerCase()`. Reviewed:\n // PR #24 codex (P2).\n const meta = member.context?.metadata;\n const metaEmail = typeof meta?.email === 'string' ? meta.email : '';\n const metaDisplayRaw =\n typeof meta?.displayName === 'string'\n ? meta.displayName\n : typeof meta?.name === 'string'\n ? meta.name\n : '';\n if (metaEmail.toLowerCase().includes(needle)) return true;\n if (metaDisplayRaw.toLowerCase().includes(needle)) return true;\n\n // Last resort: raw userId — keeps the previous behaviour for members with\n // neither a resolved profile nor a denormalized identity yet.\n return member.userId.toLowerCase().includes(needle);\n });\n\n const selectedMembers = useMemo(\n () => filteredMembers.filter((member) => selectedMemberIds.has(member.id)),\n [filteredMembers, selectedMemberIds]\n );\n\n const selectedInvitations = useMemo(\n () => pendingInvitations.filter((invitation) => selectedInvitationIds.has(invitation.id)),\n [pendingInvitations, selectedInvitationIds]\n );\n\n const allVisibleMembersSelected =\n filteredMembers.length > 0 &&\n filteredMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelectedMembers =\n filteredMembers.some((member) => selectedMemberIds.has(member.id)) &&\n !allVisibleMembersSelected;\n\n const allVisibleInvitationsSelected =\n pendingInvitations.length > 0 &&\n pendingInvitations.every((invitation) => selectedInvitationIds.has(invitation.id));\n const partiallySelectedInvitations =\n pendingInvitations.some((invitation) => selectedInvitationIds.has(invitation.id)) &&\n !allVisibleInvitationsSelected;\n\n // Clear selection on unmount\n useEffect(() => {\n return () => clearMemberSelection();\n }, [clearMemberSelection]);\n\n // Redirect to the canonical workspace invite page when arriving through\n // older members deep links that used ?invite=true.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('invite') === 'true' && workspaceId) {\n const next = new URLSearchParams(searchParams);\n next.delete('invite');\n setSearchParams(next, { replace: true });\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`), { replace: true });\n }\n }, [searchParams, setSearchParams, workspaceId, navigate, withCurrentSearch, basePath]);\n\n const {\n canListMembers,\n canInviteMember,\n canRemoveMember,\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 <Skeleton className=\"h-64\" />\n </div>\n );\n }\n\n if (!canListMembers && !canInviteMember) {\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, 'membersList.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'membersList.noPermissionToViewMembers',\n \"You don't have permission to view workspace members.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleInviteMember = () => {\n if (workspaceId) {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`));\n }\n };\n\n const toggleAllMembers = (checked: boolean) => {\n filteredMembers.forEach((member) => {\n const isSelected = selectedMemberIds.has(member.id);\n if (checked !== isSelected) {\n toggleMemberSelection(member.id);\n }\n });\n };\n\n const toggleInvitationSelection = (invitationId: string, checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(invitationId);\n } else {\n next.delete(invitationId);\n }\n return next;\n });\n };\n\n const toggleAllInvitations = (checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n pendingInvitations.forEach((invitation) => {\n if (checked) {\n next.add(invitation.id);\n } else {\n next.delete(invitation.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkRemoveMembers = async () => {\n if (selectedMembers.length === 0) return;\n void nativeImpact('medium');\n const message = `${tWithFallback(t, 'membersList.bulkRemoveConfirmPrefix', 'Remove')} ${selectedMembers.length} ${\n selectedMembers.length === 1\n ? tWithFallback(t, 'membersList.bulkRemoveConfirmSingular', 'selected member')\n : tWithFallback(t, 'membersList.bulkRemoveConfirmPlural', 'selected members')\n } ${tWithFallback(t, 'membersList.bulkRemoveConfirmSuffix', 'from this workspace?')}`;\n const native = await nativeConfirm({\n title: 'Remove members',\n message,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n selectedMembers.forEach((member) => {\n removeUserFromWorkspace(member.userId);\n });\n clearMemberSelection();\n };\n\n const handleBulkResendInvitations = () => {\n if (selectedInvitations.length === 0) return;\n selectedInvitations.forEach((invitation) => {\n resendInvitation(invitation.id, {\n onSuccess: () => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n next.delete(invitation.id);\n return next;\n });\n },\n });\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 {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'membersList.pageTitle', 'Members')}\n </h1>\n <p className=\"text-text-secondary text-sm sm:text-base\">\n {tWithFallback(\n t,\n 'membersList.pageSubtitle',\n 'Manage workspace members and invitations'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-2\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToInvite',\n \"You don't have permission to invite members\"\n )}\n >\n <Button onClick={handleInviteMember} disabled={!workspaceId}>\n <UserPlus className=\"size-4 sm:mr-2\" />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'membersList.inviteMember', 'Invite Member')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search & Stats */}\n <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <Input\n placeholder={tWithFallback(t, 'membersList.searchPlaceholder', 'Search members...')}\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n\n {hasPendingInvitations && (\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-sm\">\n {pendingInvitations.length}{' '}\n {pendingInvitations.length !== 1\n ? tWithFallback(t, 'membersList.pendingInvitations', 'Pending Invitations')\n : tWithFallback(t, 'membersList.pendingInvitation', 'Pending Invitation')}\n </Badge>\n </div>\n )}\n </div>\n\n {filteredMembers.length > 0 && (\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={allVisibleMembersSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelectedMembers && !allVisibleMembersSelected;\n }\n }}\n onChange={(event) => toggleAllMembers(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'membersList.selectAllMembersAriaLabel',\n 'Select all workspace members'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(\n t,\n 'membersList.selectAllVisibleMembers',\n 'Select all visible members'\n )}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedMemberIds.size} {tWithFallback(t, 'membersList.selected', 'selected')}\n </span>\n {selectedMemberIds.size > 0 && (\n <Button variant=\"ghost\" size=\"sm\" onClick={clearMemberSelection}>\n {tWithFallback(t, 'membersList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {canRemoveMember && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkRemoveMembers}\n disabled={selectedMembers.length === 0 || isRemovingMembers}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'membersList.removeSelected', 'Remove Selected')}\n {selectedMembers.length > 0 ? ` (${selectedMembers.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <Skeleton key={i} className=\"h-20 w-full\" />\n ))}\n </div>\n )}\n\n {/* Error State */}\n {!isLoading && membersError && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-status-error-bg/10 p-6 mb-4\">\n <AlertCircle size={48} className=\"text-status-error-text\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'membersList.failedToLoad', 'Failed to Load Members')}\n </h3>\n <p className=\"text-text-secondary max-w-sm\">\n {membersError instanceof Error\n ? membersError.message\n : tWithFallback(\n t,\n 'membersList.errorLoadingMembers',\n 'An error occurred while loading members'\n )}\n </p>\n </div>\n )}\n\n {/* Empty State - No Workspace */}\n {!isLoading && !membersError && !workspaceId && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-6 mb-4\">\n <Users size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'membersList.noWorkspaceSelected', 'No Workspace Selected')}\n </h3>\n <p className=\"text-text-secondary max-w-sm\">\n {tWithFallback(\n t,\n 'membersList.selectWorkspacePrompt',\n 'Select a workspace to view and manage its members'\n )}\n </p>\n </div>\n )}\n\n {/* Empty State - No Members */}\n {!isLoading && !membersError && workspaceId && !hasMembers && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-6 mb-4\">\n <Users size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'membersList.noMembersYet', 'No Members Yet')}\n </h3>\n <p className=\"text-text-secondary max-w-sm mb-4\">\n {tWithFallback(\n t,\n 'membersList.noMembersPrompt',\n 'Start building your team by inviting members to this workspace'\n )}\n </p>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToInvite',\n \"You don't have permission to invite members\"\n )}\n >\n <Button onClick={handleInviteMember}>\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'membersList.inviteFirstMember', 'Invite Your First Member')}\n </Button>\n </PermissionButton>\n </div>\n )}\n\n {/* Empty State - No Search Results */}\n {!isLoading &&\n !membersError &&\n workspaceId &&\n hasMembers &&\n filteredMembers.length === 0 && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-bg-sunken p-6 mb-4\">\n <Search size={48} className=\"text-text-secondary\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'membersList.noMembersFound', 'No Members Found')}\n </h3>\n <p className=\"text-text-secondary max-w-sm\">\n {tWithFallback(\n t,\n 'membersList.noMembersFoundPrompt',\n 'No members match your search criteria. Try adjusting your search.'\n )}\n </p>\n </div>\n )}\n\n {/* Members List */}\n {!isLoading && filteredMembers.length > 0 && (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'membersList.activeMembers', 'Active Members')} (\n {filteredMembers.length})\n </h2>\n </div>\n <div className=\"space-y-2\">\n {filteredMembers.map((member) => (\n <MemberListItem\n key={member.id}\n member={member}\n selected={selectedMemberIds.has(member.id)}\n onToggleSelect={(checked) => {\n const isSelected = selectedMemberIds.has(member.id);\n if (checked !== isSelected) {\n toggleMemberSelection(member.id);\n }\n }}\n canRemove={canRemoveMember}\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Pending Invitations Section - Now at bottom */}\n {!isLoading && hasPendingInvitations && (\n <div className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'membersList.pendingInvitationsHeader', 'Pending Invitations')}\n </h2>\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={allVisibleInvitationsSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate =\n partiallySelectedInvitations && !allVisibleInvitationsSelected;\n }\n }}\n onChange={(event) => toggleAllInvitations(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'membersList.selectAllInvitationsAriaLabel',\n 'Select all pending invitations'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(t, 'membersList.selectAllInvitations', 'Select all invitations')}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedInvitationIds.size}{' '}\n {tWithFallback(t, 'membersList.selected', 'selected')}\n </span>\n {selectedInvitationIds.size > 0 && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setSelectedInvitationIds(new Set())}\n >\n {tWithFallback(t, 'membersList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {canInviteMember && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkResendInvitations}\n disabled={selectedInvitations.length === 0 || isResending}\n >\n {tWithFallback(t, 'membersList.resendSelected', 'Resend Selected')}\n {selectedInvitations.length > 0 ? ` (${selectedInvitations.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n <div className=\"space-y-2\">\n {pendingInvitations.map((invitation) => (\n <div\n key={invitation.id}\n className=\"flex items-center justify-between p-4 border rounded-lg bg-bg-surface\"\n >\n <div className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={selectedInvitationIds.has(invitation.id)}\n onChange={(event) =>\n toggleInvitationSelection(invitation.id, event.target.checked)\n }\n aria-label={`${tWithFallback(t, 'membersList.selectInvitationAriaLabel', 'Select invitation')} ${invitation.email}`}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <div className=\"size-10 rounded-full bg-status-warning/10 flex items-center justify-center transition-colors\">\n <Users className=\"size-5 text-status-warning\" />\n </div>\n <div>\n <p className=\"font-medium text-sm\">{invitation.email}</p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(t, 'membersList.invited', 'Invited')}{' '}\n {new Date(invitation.createdAt).toLocaleDateString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToResend',\n \"You don't have permission to resend invitations\"\n )}\n >\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleResendInvitation(invitation.id)}\n disabled={isResending && resendingId === invitation.id}\n >\n <RefreshCw\n className={`size-4 mr-1 ${isResending && resendingId === invitation.id ? 'animate-spin' : ''}`}\n />\n {isResending && resendingId === invitation.id\n ? tWithFallback(t, 'membersList.sending', 'Sending...')\n : tWithFallback(t, 'membersList.resend', 'Resend')}\n </Button>\n </PermissionButton>\n <Badge\n variant=\"outline\"\n className=\"bg-status-warning/10 text-status-warning border-status-warning/30 transition-colors\"\n >\n {tWithFallback(t, 'membersList.pending', 'Pending')}\n </Badge>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MembersListPage.js","names":[],"sources":["../../src/pages/MembersListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useNavigate, useParams, useSearchParams } from 'react-router-dom';\nimport { Users, UserPlus, Search, AlertCircle, RefreshCw } from 'lucide-react';\nimport {\n Button,\n Input,\n Skeleton,\n Badge,\n PageContainer,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { useActorProfiles, type ActorInput, type ActorProfileMap } from '@burdenoff/fe-libs/shared';\nimport { nativeConfirm, nativeImpact } from '../utils/nativeBridge';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useWorkspaceMembers, useWorkspaceInvitations } from '../hooks/useWorkspaceMembers';\nimport { useRemoveUserFromWorkspace, useResendInvitation } from '../hooks/useMemberMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport { MemberListItem } from '../components/MemberListItem';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\n\nexport function MembersListPage() {\n const { t } = useI18n();\n\n const {\n memberFilters,\n setMemberFilters,\n selectedMemberIds,\n toggleMemberSelection,\n clearMemberSelection,\n } = useWorkspacesStore();\n const navigate = useNavigate();\n const { workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { basePath = '/workspaces' } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { workspaceId: urlWorkspaceId } = useParams<{ workspaceId: string }>();\n\n // Prefer URL workspaceId over context (user navigated to specific workspace).\n // WorkspaceContextPathSync keeps this URL segment in step with the active context\n // on a context switch, so the override can't point at a stale workspace.\n // (BOFF-2805 follow-up)\n const workspaceId = urlWorkspaceId || contextWorkspaceId;\n\n useWorkspacesPageView('workspaces.members', { workspaceId, requireParams: true });\n\n const [searchValue, setSearchValue] = useState(memberFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n setMemberFilters({ search: debouncedSearch });\n }, [debouncedSearch, setMemberFilters]);\n\n // Fetch members and invitations\n const {\n data: membersData,\n isLoading: isLoadingMembers,\n error: membersError,\n } = useWorkspaceMembers(workspaceId || '');\n\n const { data: invitationsData, isLoading: isLoadingInvitations } = useWorkspaceInvitations(\n workspaceId || '',\n 'PENDING'\n );\n\n const { mutate: resendInvitation, isPending: isResending } = useResendInvitation();\n const { mutate: removeUserFromWorkspace, isPending: isRemovingMembers } =\n useRemoveUserFromWorkspace();\n const [resendingId, setResendingId] = useState<string | null>(null);\n const [selectedInvitationIds, setSelectedInvitationIds] = useState<Set<string>>(new Set());\n\n const handleResendInvitation = (invitationId: string) => {\n setResendingId(invitationId);\n resendInvitation(invitationId, {\n onSuccess: () => {\n setResendingId(null);\n },\n onError: (error) => {\n console.error('Failed to resend invitation:', error);\n setResendingId(null);\n },\n });\n };\n\n const members = useMemo(() => membersData?.items || [], [membersData?.items]);\n const pendingInvitations = useMemo(() => invitationsData?.items || [], [invitationsData?.items]);\n const isLoading = isLoadingMembers || isLoadingInvitations;\n const hasMembers = members.length > 0;\n const hasPendingInvitations = pendingInvitations.length > 0;\n\n // Resolve every member's userId to displayName + email via the shared\n // ActorProfile cache so search can match human-friendly fields instead of\n // raw UUIDs.\n const { apiGatewayUrl, authToken, organizationId } = useWorkspacesContext();\n const memberActors = useMemo<ActorInput[]>(\n () => members.map((m) => ({ actorId: m.userId, actorType: 'user' })),\n [members]\n );\n const profileMap: ActorProfileMap = useActorProfiles(memberActors, {\n apiGatewayUrl,\n authToken,\n orgId: organizationId,\n workspaceId,\n });\n\n // Filter members by resolved identity (displayName, email) — fall back to\n // raw userId only if the profile hasn't resolved yet so search still works\n // during the brief loading window.\n const filteredMembers = members.filter((member) => {\n if (!debouncedSearch) return true;\n const needle = debouncedSearch.toLowerCase();\n\n // Primary: the shared ActorProfile cache — canonical, works for every\n // member regardless of how they joined.\n const profile = profileMap[member.userId];\n if (profile) {\n if (profile.displayName.toLowerCase().includes(needle)) return true;\n if (profile.email?.toLowerCase().includes(needle)) return true;\n }\n\n // Fallback: the denormalized identity surfaced by the backend through\n // `WorkspaceMember.context.metadata` (populated from\n // `WorkspaceMember.email` for members who joined via invitation — PR #66).\n // Available synchronously, so search still matches during the brief window\n // before the ActorProfile lookup resolves.\n //\n // The metadata field is typed `Record<string, unknown>`, so we MUST guard\n // every read with `typeof === 'string'`. A non-string value (e.g. a number,\n // null, or a nested object accidentally surfaced by a future backend\n // mapper) would otherwise crash the page on `.toLowerCase()`. Reviewed:\n // PR #24 codex (P2).\n const meta = member.context?.metadata;\n const metaEmail = typeof meta?.email === 'string' ? meta.email : '';\n const metaDisplayRaw =\n typeof meta?.displayName === 'string'\n ? meta.displayName\n : typeof meta?.name === 'string'\n ? meta.name\n : '';\n if (metaEmail.toLowerCase().includes(needle)) return true;\n if (metaDisplayRaw.toLowerCase().includes(needle)) return true;\n\n // Last resort: raw userId — keeps the previous behaviour for members with\n // neither a resolved profile nor a denormalized identity yet.\n return member.userId.toLowerCase().includes(needle);\n });\n\n const selectedMembers = useMemo(\n () => filteredMembers.filter((member) => selectedMemberIds.has(member.id)),\n [filteredMembers, selectedMemberIds]\n );\n\n const selectedInvitations = useMemo(\n () => pendingInvitations.filter((invitation) => selectedInvitationIds.has(invitation.id)),\n [pendingInvitations, selectedInvitationIds]\n );\n\n const allVisibleMembersSelected =\n filteredMembers.length > 0 &&\n filteredMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelectedMembers =\n filteredMembers.some((member) => selectedMemberIds.has(member.id)) &&\n !allVisibleMembersSelected;\n\n const allVisibleInvitationsSelected =\n pendingInvitations.length > 0 &&\n pendingInvitations.every((invitation) => selectedInvitationIds.has(invitation.id));\n const partiallySelectedInvitations =\n pendingInvitations.some((invitation) => selectedInvitationIds.has(invitation.id)) &&\n !allVisibleInvitationsSelected;\n\n // Clear selection on unmount\n useEffect(() => {\n return () => clearMemberSelection();\n }, [clearMemberSelection]);\n\n // Redirect to the canonical workspace invite page when arriving through\n // older members deep links that used ?invite=true.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('invite') === 'true' && workspaceId) {\n const next = new URLSearchParams(searchParams);\n next.delete('invite');\n setSearchParams(next, { replace: true });\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`), { replace: true });\n }\n }, [searchParams, setSearchParams, workspaceId, navigate, withCurrentSearch, basePath]);\n\n const {\n canListMembers,\n canInviteMember,\n canRemoveMember,\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 <Skeleton className=\"h-64\" />\n </div>\n );\n }\n\n if (!canListMembers && !canInviteMember) {\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, 'membersList.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'membersList.noPermissionToViewMembers',\n \"You don't have permission to view workspace members.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n const handleInviteMember = () => {\n if (workspaceId) {\n navigate(withCurrentSearch(`${basePath}/${workspaceId}/invite`));\n }\n };\n\n const toggleAllMembers = (checked: boolean) => {\n filteredMembers.forEach((member) => {\n const isSelected = selectedMemberIds.has(member.id);\n if (checked !== isSelected) {\n toggleMemberSelection(member.id);\n }\n });\n };\n\n const toggleInvitationSelection = (invitationId: string, checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(invitationId);\n } else {\n next.delete(invitationId);\n }\n return next;\n });\n };\n\n const toggleAllInvitations = (checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n pendingInvitations.forEach((invitation) => {\n if (checked) {\n next.add(invitation.id);\n } else {\n next.delete(invitation.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkRemoveMembers = async () => {\n if (selectedMembers.length === 0) return;\n void nativeImpact('medium');\n const message = `${tWithFallback(t, 'membersList.bulkRemoveConfirmPrefix', 'Remove')} ${selectedMembers.length} ${\n selectedMembers.length === 1\n ? tWithFallback(t, 'membersList.bulkRemoveConfirmSingular', 'selected member')\n : tWithFallback(t, 'membersList.bulkRemoveConfirmPlural', 'selected members')\n } ${tWithFallback(t, 'membersList.bulkRemoveConfirmSuffix', 'from this workspace?')}`;\n const native = await nativeConfirm({\n title: 'Remove members',\n message,\n okButtonTitle: 'Remove',\n cancelButtonTitle: 'Cancel',\n });\n const confirmed = native === null ? window.confirm(message) : native;\n if (!confirmed) return;\n\n selectedMembers.forEach((member) => {\n removeUserFromWorkspace(member.userId);\n });\n clearMemberSelection();\n };\n\n const handleBulkResendInvitations = () => {\n if (selectedInvitations.length === 0) return;\n selectedInvitations.forEach((invitation) => {\n resendInvitation(invitation.id, {\n onSuccess: () => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n next.delete(invitation.id);\n return next;\n });\n },\n });\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 {/* Header */}\n <div className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4\">\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'membersList.pageTitle', 'Members')}\n </h1>\n <p className=\"text-text-secondary text-sm sm:text-base\">\n {tWithFallback(\n t,\n 'membersList.pageSubtitle',\n 'Manage workspace members and invitations'\n )}\n </p>\n </div>\n\n <div className=\"flex gap-2\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToInvite',\n \"You don't have permission to invite members\"\n )}\n >\n <Button onClick={handleInviteMember} disabled={!workspaceId}>\n <UserPlus className=\"size-4 sm:mr-2\" />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'membersList.inviteMember', 'Invite Member')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search & Stats */}\n <div className=\"flex flex-col sm:flex-row gap-3 sm:gap-4\">\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-secondary\" />\n <Input\n placeholder={tWithFallback(t, 'membersList.searchPlaceholder', 'Search members...')}\n value={searchValue}\n onChange={(e) => setSearchValue(e.target.value)}\n className=\"pl-9\"\n />\n </div>\n\n {hasPendingInvitations && (\n <div className=\"flex items-center gap-2\">\n <Badge variant=\"secondary\" className=\"text-sm\">\n {pendingInvitations.length}{' '}\n {pendingInvitations.length !== 1\n ? tWithFallback(t, 'membersList.pendingInvitations', 'Pending Invitations')\n : tWithFallback(t, 'membersList.pendingInvitation', 'Pending Invitation')}\n </Badge>\n </div>\n )}\n </div>\n\n {filteredMembers.length > 0 && (\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={allVisibleMembersSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate = partiallySelectedMembers && !allVisibleMembersSelected;\n }\n }}\n onChange={(event) => toggleAllMembers(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'membersList.selectAllMembersAriaLabel',\n 'Select all workspace members'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(\n t,\n 'membersList.selectAllVisibleMembers',\n 'Select all visible members'\n )}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedMemberIds.size} {tWithFallback(t, 'membersList.selected', 'selected')}\n </span>\n {selectedMemberIds.size > 0 && (\n <Button variant=\"ghost\" size=\"sm\" onClick={clearMemberSelection}>\n {tWithFallback(t, 'membersList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {canRemoveMember && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkRemoveMembers}\n disabled={selectedMembers.length === 0 || isRemovingMembers}\n className=\"text-status-error-text\"\n >\n {tWithFallback(t, 'membersList.removeSelected', 'Remove Selected')}\n {selectedMembers.length > 0 ? ` (${selectedMembers.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n )}\n\n {/* Loading State */}\n {isLoading && (\n <div className=\"space-y-3\">\n {[...Array(5)].map((_, i) => (\n <Skeleton key={i} className=\"h-20 w-full\" />\n ))}\n </div>\n )}\n\n {/* Error State */}\n {!isLoading && membersError && (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <div className=\"rounded-full bg-status-error-bg/10 p-6 mb-4\">\n <AlertCircle size={48} className=\"text-status-error-text\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'membersList.failedToLoad', 'Failed to Load Members')}\n </h3>\n <p className=\"text-text-secondary max-w-sm\">\n {membersError instanceof Error\n ? membersError.message\n : tWithFallback(\n t,\n 'membersList.errorLoadingMembers',\n 'An error occurred while loading members'\n )}\n </p>\n </div>\n )}\n\n {/* Empty State - No Workspace */}\n {!isLoading && !membersError && !workspaceId && (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={tWithFallback(t, 'membersList.noWorkspaceSelected', 'No Workspace Selected')}\n description={tWithFallback(\n t,\n 'membersList.selectWorkspacePrompt',\n 'Select a workspace to view and manage its members'\n )}\n />\n )}\n\n {/* Empty State - No Members */}\n {!isLoading && !membersError && workspaceId && !hasMembers && (\n <IllustratedEmptyState\n illustration=\"empty-people\"\n title={tWithFallback(t, 'membersList.noMembersYet', 'No Members Yet')}\n description={tWithFallback(\n t,\n 'membersList.noMembersPrompt',\n 'Start building your team by inviting members to this workspace'\n )}\n action={\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToInvite',\n \"You don't have permission to invite members\"\n )}\n >\n <Button onClick={handleInviteMember}>\n <UserPlus className=\"mr-2 size-4\" />\n {tWithFallback(t, 'membersList.inviteFirstMember', 'Invite Your First Member')}\n </Button>\n </PermissionButton>\n }\n />\n )}\n\n {/* Empty State - No Search Results */}\n {!isLoading &&\n !membersError &&\n workspaceId &&\n hasMembers &&\n filteredMembers.length === 0 && (\n <IllustratedEmptyState\n illustration=\"empty-search\"\n title={tWithFallback(t, 'membersList.noMembersFound', 'No Members Found')}\n description={tWithFallback(\n t,\n 'membersList.noMembersFoundPrompt',\n 'No members match your search criteria. Try adjusting your search.'\n )}\n />\n )}\n\n {/* Members List */}\n {!isLoading && filteredMembers.length > 0 && (\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'membersList.activeMembers', 'Active Members')} (\n {filteredMembers.length})\n </h2>\n </div>\n <div className=\"space-y-2\">\n {filteredMembers.map((member) => (\n <MemberListItem\n key={member.id}\n member={member}\n selected={selectedMemberIds.has(member.id)}\n onToggleSelect={(checked) => {\n const isSelected = selectedMemberIds.has(member.id);\n if (checked !== isSelected) {\n toggleMemberSelection(member.id);\n }\n }}\n canRemove={canRemoveMember}\n />\n ))}\n </div>\n </div>\n )}\n\n {/* Pending Invitations Section - Now at bottom */}\n {!isLoading && hasPendingInvitations && (\n <div className=\"space-y-3\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'membersList.pendingInvitationsHeader', 'Pending Invitations')}\n </h2>\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={allVisibleInvitationsSelected}\n ref={(input) => {\n if (input) {\n input.indeterminate =\n partiallySelectedInvitations && !allVisibleInvitationsSelected;\n }\n }}\n onChange={(event) => toggleAllInvitations(event.target.checked)}\n aria-label={tWithFallback(\n t,\n 'membersList.selectAllInvitationsAriaLabel',\n 'Select all pending invitations'\n )}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <span>\n {tWithFallback(t, 'membersList.selectAllInvitations', 'Select all invitations')}\n </span>\n </label>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-text-secondary\">\n {selectedInvitationIds.size}{' '}\n {tWithFallback(t, 'membersList.selected', 'selected')}\n </span>\n {selectedInvitationIds.size > 0 && (\n <Button\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => setSelectedInvitationIds(new Set())}\n >\n {tWithFallback(t, 'membersList.clearSelection', 'Clear selection')}\n </Button>\n )}\n {canInviteMember && (\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={handleBulkResendInvitations}\n disabled={selectedInvitations.length === 0 || isResending}\n >\n {tWithFallback(t, 'membersList.resendSelected', 'Resend Selected')}\n {selectedInvitations.length > 0 ? ` (${selectedInvitations.length})` : ''}\n </Button>\n )}\n </div>\n </div>\n <div className=\"space-y-2\">\n {pendingInvitations.map((invitation) => (\n <div\n key={invitation.id}\n className=\"flex items-center justify-between p-4 border rounded-lg bg-bg-surface\"\n >\n <div className=\"flex items-center gap-3\">\n <input\n type=\"checkbox\"\n checked={selectedInvitationIds.has(invitation.id)}\n onChange={(event) =>\n toggleInvitationSelection(invitation.id, event.target.checked)\n }\n aria-label={`${tWithFallback(t, 'membersList.selectInvitationAriaLabel', 'Select invitation')} ${invitation.email}`}\n className=\"size-4 rounded border-border-subtle text-primary\"\n />\n <div className=\"size-10 rounded-full bg-status-warning/10 flex items-center justify-center transition-colors\">\n <Users className=\"size-5 text-status-warning\" />\n </div>\n <div>\n <p className=\"font-medium text-sm\">{invitation.email}</p>\n <p className=\"text-xs text-text-secondary\">\n {tWithFallback(t, 'membersList.invited', 'Invited')}{' '}\n {new Date(invitation.createdAt).toLocaleDateString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'membersList.noPermissionToResend',\n \"You don't have permission to resend invitations\"\n )}\n >\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => handleResendInvitation(invitation.id)}\n disabled={isResending && resendingId === invitation.id}\n >\n <RefreshCw\n className={`size-4 mr-1 ${isResending && resendingId === invitation.id ? 'animate-spin' : ''}`}\n />\n {isResending && resendingId === invitation.id\n ? tWithFallback(t, 'membersList.sending', 'Sending...')\n : tWithFallback(t, 'membersList.resend', 'Resend')}\n </Button>\n </PermissionButton>\n <Badge\n variant=\"outline\"\n className=\"bg-status-warning/10 text-status-warning border-status-warning/30 transition-colors\"\n >\n {tWithFallback(t, 'membersList.pending', 'Pending')}\n </Badge>\n </div>\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n </PageContainer>\n );\n}\n"],"mappings":"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"}
|
|
@@ -5,9 +5,9 @@ import { useWorkspacesPageView as r } from "../hooks/useWorkspacesPageView.js";
|
|
|
5
5
|
import { useAcceptInvitation as i, useRejectInvitation as a } from "../hooks/useMemberMutations.js";
|
|
6
6
|
import { getProjectDisplayNames as o, getRoleDisplayNames as s, getWorkspaceDisplayName as c } from "../utils/invitationDisplay.js";
|
|
7
7
|
import { useMemo as l, useState as u } from "react";
|
|
8
|
-
import { AlertTriangle as d, CheckCircle as f, Home as p,
|
|
9
|
-
import { jsx as
|
|
10
|
-
import { Badge as
|
|
8
|
+
import { AlertTriangle as d, CheckCircle as f, Home as p, Mail as m, RefreshCw as h, XCircle as g } from "lucide-react";
|
|
9
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
10
|
+
import { Badge as y, Button as b, GlassCard as x, IllustratedEmptyState as S, PageContainer as C, Skeleton as w } from "@burdenoff/fe-libs/ui";
|
|
11
11
|
import { useNavigate as T } from "react-router-dom";
|
|
12
12
|
import { useI18n as E } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
13
13
|
import { ActorIdentity as D, useActorProfiles as O } from "@burdenoff/fe-libs/shared";
|
|
@@ -53,196 +53,187 @@ function A() {
|
|
|
53
53
|
}
|
|
54
54
|
});
|
|
55
55
|
};
|
|
56
|
-
return !N || !M?.email ? /* @__PURE__ */
|
|
56
|
+
return !N || !M?.email ? /* @__PURE__ */ _(C, {
|
|
57
57
|
padded: !1,
|
|
58
|
-
children: /* @__PURE__ */
|
|
58
|
+
children: /* @__PURE__ */ _("div", {
|
|
59
59
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
60
|
-
children: /* @__PURE__ */
|
|
60
|
+
children: /* @__PURE__ */ v("div", {
|
|
61
61
|
className: "rounded-lg border border-border-seam bg-bg-surface p-6 text-center",
|
|
62
62
|
children: [
|
|
63
|
-
/* @__PURE__ */
|
|
64
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ _(d, { className: "mx-auto mb-3 size-10 text-text-secondary" }),
|
|
64
|
+
/* @__PURE__ */ _("h1", {
|
|
65
65
|
className: "text-2xl font-bold",
|
|
66
66
|
children: n(A, "pendingInvites.signInTitle", "Sign in to view your pending invites")
|
|
67
67
|
}),
|
|
68
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ _("p", {
|
|
69
69
|
className: "mt-2 text-text-secondary",
|
|
70
70
|
children: n(A, "pendingInvites.signInDescription", "You need an authenticated account before you can review or respond to workspace invitations.")
|
|
71
71
|
}),
|
|
72
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ _("div", {
|
|
73
73
|
className: "mt-4 flex justify-center",
|
|
74
|
-
children: /* @__PURE__ */
|
|
74
|
+
children: /* @__PURE__ */ v(b, {
|
|
75
75
|
onClick: Z,
|
|
76
76
|
variant: "outline",
|
|
77
|
-
children: [/* @__PURE__ */
|
|
77
|
+
children: [/* @__PURE__ */ _(p, { className: "mr-2 size-4" }), n(A, "pendingInvites.backToWorkspaces", "Back to Workspaces")]
|
|
78
78
|
})
|
|
79
79
|
})
|
|
80
80
|
]
|
|
81
81
|
})
|
|
82
82
|
})
|
|
83
|
-
}) : /* @__PURE__ */
|
|
83
|
+
}) : /* @__PURE__ */ _(C, {
|
|
84
84
|
padded: !1,
|
|
85
|
-
children: /* @__PURE__ */
|
|
85
|
+
children: /* @__PURE__ */ v("div", {
|
|
86
86
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
87
87
|
children: [
|
|
88
|
-
/* @__PURE__ */
|
|
88
|
+
/* @__PURE__ */ v("div", {
|
|
89
89
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
90
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
|
|
91
91
|
className: "text-2xl sm:text-3xl font-bold",
|
|
92
92
|
children: n(A, "pendingInvites.pageTitle", "Pending Invites")
|
|
93
|
-
}), /* @__PURE__ */
|
|
93
|
+
}), /* @__PURE__ */ _("p", {
|
|
94
94
|
className: "text-sm sm:text-base text-text-secondary",
|
|
95
95
|
children: n(A, "pendingInvites.pageSubtitle", "Review and respond to workspace invitations sent to your account.")
|
|
96
|
-
})] }), /* @__PURE__ */
|
|
96
|
+
})] }), /* @__PURE__ */ v("div", {
|
|
97
97
|
className: "flex items-center gap-2",
|
|
98
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ _(y, {
|
|
99
99
|
variant: "secondary",
|
|
100
100
|
children: W.length
|
|
101
|
-
}), /* @__PURE__ */
|
|
101
|
+
}), /* @__PURE__ */ v(b, {
|
|
102
102
|
variant: "outline",
|
|
103
103
|
onClick: Z,
|
|
104
|
-
children: [/* @__PURE__ */
|
|
104
|
+
children: [/* @__PURE__ */ _(p, { className: "mr-2 size-4" }), n(A, "pendingInvites.viewWorkspaces", "View Workspaces")]
|
|
105
105
|
})]
|
|
106
106
|
})]
|
|
107
107
|
}),
|
|
108
|
-
z ? /* @__PURE__ */
|
|
108
|
+
z ? /* @__PURE__ */ v("div", {
|
|
109
109
|
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",
|
|
110
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ _(d, { className: "mt-0.5 size-4 shrink-0" }), /* @__PURE__ */ _("span", { children: z })]
|
|
111
111
|
}) : null,
|
|
112
|
-
U ? /* @__PURE__ */
|
|
112
|
+
U ? /* @__PURE__ */ _("div", {
|
|
113
113
|
className: "rounded-lg border border-status-error-border/30 bg-status-error-bg/10 p-4 text-sm text-status-error-text",
|
|
114
114
|
children: U instanceof Error ? U.message : n(A, "pendingInvites.loadError", "Failed to load pending invitations")
|
|
115
115
|
}) : null,
|
|
116
|
-
H ? /* @__PURE__ */
|
|
116
|
+
H ? /* @__PURE__ */ _("div", {
|
|
117
117
|
className: "space-y-4",
|
|
118
|
-
children: Array.from({ length: 3 }).map((e, t) => /* @__PURE__ */
|
|
118
|
+
children: Array.from({ length: 3 }).map((e, t) => /* @__PURE__ */ v("div", {
|
|
119
119
|
className: "rounded-lg border border-border-seam bg-bg-surface p-5 space-y-3",
|
|
120
120
|
children: [
|
|
121
|
-
/* @__PURE__ */
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ _(w, { className: "h-5 w-40" }),
|
|
122
|
+
/* @__PURE__ */ _(w, { className: "h-4 w-64" }),
|
|
123
|
+
/* @__PURE__ */ _(w, { className: "h-4 w-52" }),
|
|
124
|
+
/* @__PURE__ */ v("div", {
|
|
125
125
|
className: "flex gap-2 pt-2",
|
|
126
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ _(w, { className: "h-10 w-36" }), /* @__PURE__ */ _(w, { className: "h-10 w-28" })]
|
|
127
127
|
})
|
|
128
128
|
]
|
|
129
129
|
}, t))
|
|
130
|
-
}) : W.length === 0 ? /* @__PURE__ */
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
className: "text-xl font-semibold",
|
|
136
|
-
children: n(A, "pendingInvites.emptyTitle", "No pending invites")
|
|
137
|
-
}),
|
|
138
|
-
/* @__PURE__ */ v("p", {
|
|
139
|
-
className: "mt-2 text-text-secondary",
|
|
140
|
-
children: n(A, "pendingInvites.emptyDescription", "When someone invites you to a workspace, it will appear here.")
|
|
141
|
-
})
|
|
142
|
-
]
|
|
143
|
-
}) : /* @__PURE__ */ v("div", {
|
|
130
|
+
}) : W.length === 0 ? /* @__PURE__ */ _(S, {
|
|
131
|
+
illustration: "empty-notifications",
|
|
132
|
+
title: n(A, "pendingInvites.emptyTitle", "No pending invites"),
|
|
133
|
+
description: n(A, "pendingInvites.emptyDescription", "When someone invites you to a workspace, it will appear here.")
|
|
134
|
+
}) : /* @__PURE__ */ _("div", {
|
|
144
135
|
className: "space-y-4",
|
|
145
136
|
children: W.map((e) => {
|
|
146
137
|
let t = e.invitedById ? G[e.invitedById] : void 0, r = t?.displayName || t?.email || e.invitedById || "—", i = k(e.expiresAt), a = k(e.createdAt), l = c(e), u = s(e), d = o(e), p = L === e.id && X;
|
|
147
|
-
return /* @__PURE__ */
|
|
138
|
+
return /* @__PURE__ */ _(x, {
|
|
148
139
|
treatment: "insight",
|
|
149
140
|
className: "border-border-seam p-5",
|
|
150
|
-
children: /* @__PURE__ */
|
|
141
|
+
children: /* @__PURE__ */ v("div", {
|
|
151
142
|
className: "flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",
|
|
152
|
-
children: [/* @__PURE__ */
|
|
143
|
+
children: [/* @__PURE__ */ v("div", {
|
|
153
144
|
className: "space-y-3 min-w-0",
|
|
154
145
|
children: [
|
|
155
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ v("div", {
|
|
156
147
|
className: "flex flex-wrap items-center gap-2",
|
|
157
|
-
children: [/* @__PURE__ */
|
|
148
|
+
children: [/* @__PURE__ */ v("div", {
|
|
158
149
|
className: "flex items-center gap-2 font-semibold text-lg",
|
|
159
|
-
children: [/* @__PURE__ */
|
|
160
|
-
}), /* @__PURE__ */
|
|
150
|
+
children: [/* @__PURE__ */ _(m, { className: "size-4 text-primary" }), /* @__PURE__ */ _("span", { children: e.email })]
|
|
151
|
+
}), /* @__PURE__ */ _(y, {
|
|
161
152
|
variant: "secondary",
|
|
162
153
|
children: e.status
|
|
163
154
|
})]
|
|
164
155
|
}),
|
|
165
|
-
/* @__PURE__ */
|
|
156
|
+
/* @__PURE__ */ v("dl", {
|
|
166
157
|
className: "grid gap-2 text-sm text-text-secondary sm:grid-cols-2",
|
|
167
158
|
children: [
|
|
168
|
-
/* @__PURE__ */
|
|
169
|
-
/* @__PURE__ */
|
|
159
|
+
/* @__PURE__ */ v("div", { children: [
|
|
160
|
+
/* @__PURE__ */ _("dt", {
|
|
170
161
|
className: "font-medium text-text-primary",
|
|
171
162
|
children: n(A, "pendingInvites.workspaceLabel", "Workspace")
|
|
172
163
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ _("dd", {
|
|
174
165
|
className: "break-words font-medium text-text-primary",
|
|
175
166
|
children: l
|
|
176
167
|
}),
|
|
177
|
-
e.workspaceId ? /* @__PURE__ */
|
|
168
|
+
e.workspaceId ? /* @__PURE__ */ _("dd", {
|
|
178
169
|
className: "mt-1 break-all font-mono text-xs text-text-secondary",
|
|
179
170
|
children: e.workspaceId
|
|
180
171
|
}) : null
|
|
181
172
|
] }),
|
|
182
|
-
/* @__PURE__ */
|
|
173
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("dt", {
|
|
183
174
|
className: "font-medium text-text-primary",
|
|
184
175
|
children: n(A, "pendingInvites.invitedByLabel", "Invited by")
|
|
185
|
-
}), /* @__PURE__ */
|
|
176
|
+
}), /* @__PURE__ */ _("dd", {
|
|
186
177
|
className: "break-all",
|
|
187
|
-
children: t ? /* @__PURE__ */
|
|
178
|
+
children: t ? /* @__PURE__ */ _(D, {
|
|
188
179
|
profile: t,
|
|
189
180
|
variant: "inline",
|
|
190
181
|
hideIdDrilldown: !0
|
|
191
|
-
}) : /* @__PURE__ */
|
|
182
|
+
}) : /* @__PURE__ */ _("span", {
|
|
192
183
|
className: "font-medium text-text-primary",
|
|
193
184
|
children: r
|
|
194
185
|
})
|
|
195
186
|
})] }),
|
|
196
|
-
/* @__PURE__ */
|
|
187
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("dt", {
|
|
197
188
|
className: "font-medium text-text-primary",
|
|
198
189
|
children: n(A, "pendingInvites.receivedLabel", "Received")
|
|
199
|
-
}), /* @__PURE__ */
|
|
200
|
-
/* @__PURE__ */
|
|
190
|
+
}), /* @__PURE__ */ _("dd", { children: a ?? "—" })] }),
|
|
191
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("dt", {
|
|
201
192
|
className: "font-medium text-text-primary",
|
|
202
193
|
children: n(A, "pendingInvites.expiresLabel", "Expires")
|
|
203
|
-
}), /* @__PURE__ */
|
|
194
|
+
}), /* @__PURE__ */ _("dd", { children: i ?? "—" })] })
|
|
204
195
|
]
|
|
205
196
|
}),
|
|
206
|
-
/* @__PURE__ */
|
|
197
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
207
198
|
className: "text-sm font-medium text-text-primary mb-2",
|
|
208
199
|
children: n(A, "pendingInvites.rolesLabel", "Roles")
|
|
209
|
-
}), /* @__PURE__ */
|
|
200
|
+
}), /* @__PURE__ */ _("div", {
|
|
210
201
|
className: "flex flex-wrap gap-2",
|
|
211
|
-
children: u.length > 0 ? u.map((t, n) => /* @__PURE__ */
|
|
202
|
+
children: u.length > 0 ? u.map((t, n) => /* @__PURE__ */ _(y, {
|
|
212
203
|
variant: "outline",
|
|
213
204
|
children: t
|
|
214
|
-
}, `${e.id}-${e.roleIds[n] ?? t}`)) : /* @__PURE__ */
|
|
205
|
+
}, `${e.id}-${e.roleIds[n] ?? t}`)) : /* @__PURE__ */ _("span", {
|
|
215
206
|
className: "text-sm text-text-secondary",
|
|
216
207
|
children: n(A, "pendingInvites.noRoles", "No explicit roles assigned")
|
|
217
208
|
})
|
|
218
209
|
})] }),
|
|
219
|
-
/* @__PURE__ */
|
|
210
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
220
211
|
className: "text-sm font-medium text-text-primary mb-2",
|
|
221
212
|
children: n(A, "pendingInvites.projectsLabel", "Projects")
|
|
222
|
-
}), /* @__PURE__ */
|
|
213
|
+
}), /* @__PURE__ */ _("div", {
|
|
223
214
|
className: "flex flex-wrap gap-2",
|
|
224
|
-
children: d.length > 0 ? d.map((t, n) => /* @__PURE__ */
|
|
215
|
+
children: d.length > 0 ? d.map((t, n) => /* @__PURE__ */ _(y, {
|
|
225
216
|
variant: "secondary",
|
|
226
217
|
children: t
|
|
227
|
-
}, `${e.id}-project-${t}-${n}`)) : /* @__PURE__ */
|
|
218
|
+
}, `${e.id}-project-${t}-${n}`)) : /* @__PURE__ */ _("span", {
|
|
228
219
|
className: "text-sm text-text-secondary",
|
|
229
220
|
children: n(A, "pendingInvites.noProjects", "No project access will be granted automatically")
|
|
230
221
|
})
|
|
231
222
|
})] })
|
|
232
223
|
]
|
|
233
|
-
}), /* @__PURE__ */
|
|
224
|
+
}), /* @__PURE__ */ v("div", {
|
|
234
225
|
className: "flex flex-col gap-2 sm:flex-row lg:flex-col lg:min-w-44",
|
|
235
|
-
children: [/* @__PURE__ */
|
|
226
|
+
children: [/* @__PURE__ */ v(b, {
|
|
236
227
|
onClick: () => Q(e.id),
|
|
237
228
|
disabled: X,
|
|
238
229
|
className: "w-full",
|
|
239
|
-
children: [p && q ? /* @__PURE__ */
|
|
240
|
-
}), /* @__PURE__ */
|
|
230
|
+
children: [p && q ? /* @__PURE__ */ _(h, { className: "mr-2 size-4 animate-spin" }) : /* @__PURE__ */ _(f, { className: "mr-2 size-4" }), n(A, "pendingInvites.acceptButton", "Accept")]
|
|
231
|
+
}), /* @__PURE__ */ v(b, {
|
|
241
232
|
variant: "outline",
|
|
242
233
|
onClick: () => $(e.id),
|
|
243
234
|
disabled: X,
|
|
244
235
|
className: "w-full",
|
|
245
|
-
children: [p && Y ? /* @__PURE__ */
|
|
236
|
+
children: [p && Y ? /* @__PURE__ */ _(h, { className: "mr-2 size-4 animate-spin" }) : /* @__PURE__ */ _(g, { className: "mr-2 size-4" }), n(A, "pendingInvites.rejectButton", "Reject")]
|
|
246
237
|
})]
|
|
247
238
|
})]
|
|
248
239
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PendingInvitesPage.js","names":[],"sources":["../../src/pages/PendingInvitesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { Mail, RefreshCw, CheckCircle, XCircle, Inbox, AlertTriangle, Home } from 'lucide-react';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport { Badge, Button, GlassCard, PageContainer, Skeleton } 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 {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 key={index} className=\"rounded-lg border border-border-seam bg-bg-surface p-5 space-y-3\">\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 <div className=\"rounded-lg border border-border-seam bg-bg-surface p-10 text-center\">\n <Inbox className=\"mx-auto mb-4 size-12 text-text-secondary\" />\n <h2 className=\"text-xl font-semibold\">\n {tWithFallback(t, 'pendingInvites.emptyTitle', 'No pending invites')}\n </h2>\n <p className=\"mt-2 text-text-secondary\">\n {tWithFallback(\n t,\n 'pendingInvites.emptyDescription',\n 'When someone invites you to a workspace, it will appear here.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {sortedInvitations.map((invitation) => {\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 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
|
+
{"version":3,"file":"PendingInvitesPage.js","names":[],"sources":["../../src/pages/PendingInvitesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\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} 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 {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) => {\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 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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"}
|