@burdenoff/microfe-workspaces 2026.531.2 → 2026.531.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/WorkspacesRoutes.js +36 -17
- package/dist/WorkspacesRoutes.js.map +1 -1
- package/dist/components/InviteMemberDialog.js +204 -117
- package/dist/components/InviteMemberDialog.js.map +1 -1
- package/dist/components/MemberListItem.js +2 -1
- package/dist/components/MemberListItem.js.map +1 -1
- package/dist/components/ProjectMemberPickerDialog.js +211 -0
- package/dist/components/ProjectMemberPickerDialog.js.map +1 -0
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useMemberMutations.js +13 -13
- package/dist/hooks/useMemberMutations.js.map +1 -1
- package/dist/hooks/useProjectMemberMutations.js +104 -0
- package/dist/hooks/useProjectMemberMutations.js.map +1 -0
- package/dist/hooks/useProjectMembers.js +42 -0
- package/dist/hooks/useProjectMembers.js.map +1 -0
- package/dist/hooks/useWorkspaceMembers.js +60 -24
- package/dist/hooks/useWorkspaceMembers.js.map +1 -1
- package/dist/hooks/useWorkspacePermissions.js +4 -0
- package/dist/hooks/useWorkspacePermissions.js.map +1 -1
- package/dist/hooks/useWorkspacesEventEmitter.js.map +1 -1
- package/dist/index.js +16 -15
- package/dist/pages/ActivityListPage.js +5 -5
- package/dist/pages/InviteAcceptancePage.js +211 -207
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +210 -208
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +124 -120
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +249 -213
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +265 -0
- package/dist/pages/ProjectMembersPage.js.map +1 -0
- package/dist/pages/ProjectsListPage.js +9 -9
- package/dist/pages/WorkspaceDetailPage.js +119 -111
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspaceInvitePage.js +790 -0
- package/dist/pages/WorkspaceInvitePage.js.map +1 -0
- package/dist/pages/WorkspacesListPage.js +115 -100
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/utils/invitationDisplay.js +25 -0
- package/dist/utils/invitationDisplay.js.map +1 -0
- package/dist/utils/workspaceBulkInviteCsv.js +42 -0
- package/dist/utils/workspaceBulkInviteCsv.js.map +1 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MembersListPage.js","names":[],"sources":["../../src/pages/MembersListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { 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 { InviteMemberDialog } from '../components/InviteMemberDialog';\nimport type { Workspace } from '../types';\n\nexport function MembersListPage() {\n const { t } = useI18n();\n\n const {\n memberFilters,\n setMemberFilters,\n selectedMemberIds,\n toggleMemberSelection,\n clearMemberSelection,\n openInviteMemberDialog,\n } = useWorkspacesStore();\n const { workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { workspaceId: urlWorkspaceId } = useParams<{ workspaceId: string }>();\n\n // Prefer URL workspaceId over context (user navigated to specific workspace)\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 });\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 // Auto-open the invite dialog when arriving via /workspaces/members?invite=true\n // (used by dashboard widgets and deep links). Strip the param after opening so\n // the back button returns to the previous page rather than re-opening.\n const [searchParams, setSearchParams] = useSearchParams();\n useEffect(() => {\n if (searchParams.get('invite') === 'true' && workspaceId) {\n openInviteMemberDialog({ id: workspaceId } as Workspace);\n const next = new URLSearchParams(searchParams);\n next.delete('invite');\n setSearchParams(next, { replace: true });\n }\n }, [searchParams, setSearchParams, workspaceId, openInviteMemberDialog]);\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-muted-foreground\">\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 openInviteMemberDialog({ id: workspaceId } as Workspace);\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-muted-foreground 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-muted-foreground\" />\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 bg-card p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\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 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-muted-foreground\">\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-destructive\"\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-destructive/10 p-6 mb-4\">\n <AlertCircle size={48} className=\"text-destructive\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Users size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Users size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Search size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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 bg-card p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\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 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-muted-foreground\">\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-card\"\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 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-muted-foreground\">\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\n {/* Dialogs */}\n <InviteMemberDialog workspaceId={workspaceId} />\n </PageContainer>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,IAAkB;CAChC,IAAM,EAAE,SAAM,IAAS,EAEjB,EACJ,mBACA,qBACA,sBACA,0BACA,yBACA,8BACE,GAAoB,EAClB,EAAE,aAAa,OAAuB,GAAsB,EAC5D,EAAE,aAAa,OAAmB,IAAoC,EAGtE,IAAc,MAAkB;AAEtC,GAAsB,sBAAsB;EAAE;EAAa,eAAe;EAAM,CAAC;CAEjF,IAAM,CAAC,GAAa,MAAkB,EAAS,GAAc,UAAU,GAAG,EACpE,IAAkB,GAAY,GAAa,IAAI;AAGrD,SAAgB;AACd,IAAiB,EAAE,QAAQ,GAAiB,CAAC;IAC5C,CAAC,GAAiB,EAAiB,CAAC;CAGvC,IAAM,EACJ,MAAM,GACN,WAAW,IACX,OAAO,MACL,GAAoB,KAAe,GAAG,EAEpC,EAAE,MAAM,GAAiB,WAAW,OAAyB,EACjE,KAAe,IACf,UACD,EAEK,EAAE,QAAQ,GAAkB,WAAW,MAAgB,IAAqB,EAC5E,EAAE,QAAQ,IAAyB,WAAW,OAClD,IAA4B,EACxB,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAC7D,CAAC,GAAuB,KAA4B,kBAAsB,IAAI,KAAK,CAAC,EAEpF,MAA0B,MAAyB;AAEvD,EADA,EAAe,EAAa,EAC5B,EAAiB,GAAc;GAC7B,iBAAiB;AACf,MAAe,KAAK;;GAEtB,UAAU,MAAU;AAElB,IADA,QAAQ,MAAM,gCAAgC,EAAM,EACpD,EAAe,KAAK;;GAEvB,CAAC;IAGE,IAAU,QAAc,GAAa,SAAS,EAAE,EAAE,CAAC,GAAa,MAAM,CAAC,EACvE,IAAqB,QAAc,GAAiB,SAAS,EAAE,EAAE,CAAC,GAAiB,MAAM,CAAC,EAC1F,IAAY,MAAoB,IAChC,IAAa,EAAQ,SAAS,GAC9B,IAAwB,EAAmB,SAAS,GAKpD,EAAE,mBAAe,eAAW,uBAAmB,GAAsB,EAKrE,KAA8B,GAJf,QACb,EAAQ,KAAK,OAAO;EAAE,SAAS,EAAE;EAAQ,WAAW;EAAQ,EAAE,EACpE,CAAC,EAAQ,CACV,EACkE;EACjE;EACA;EACA,OAAO;EACR,CAAC,EAKI,IAAkB,EAAQ,QAAQ,MAAW;AACjD,MAAI,CAAC,EAAiB,QAAO;EAC7B,IAAM,IAAS,EAAgB,aAAa,EAItC,IAAU,GAAW,EAAO;AAClC,MAAI,MACE,EAAQ,YAAY,aAAa,CAAC,SAAS,EAAO,IAClD,EAAQ,OAAO,aAAa,CAAC,SAAS,EAAO,EAAE,QAAO;EAc5D,IAAM,IAAO,EAAO,SAAS,UACvB,IAAY,OAAO,GAAM,SAAU,WAAW,EAAK,QAAQ,IAC3D,IACJ,OAAO,GAAM,eAAgB,WACzB,EAAK,cACL,OAAO,GAAM,QAAS,WACpB,EAAK,OACL;AAMR,SALI,EAAU,aAAa,CAAC,SAAS,EAAO,IACxC,EAAe,aAAa,CAAC,SAAS,EAAO,GAAS,KAInD,EAAO,OAAO,aAAa,CAAC,SAAS,EAAO;GACnD,EAEI,IAAkB,QAChB,EAAgB,QAAQ,MAAW,EAAkB,IAAI,EAAO,GAAG,CAAC,EAC1E,CAAC,GAAiB,EAAkB,CACrC,EAEK,IAAsB,QACpB,EAAmB,QAAQ,MAAe,EAAsB,IAAI,EAAW,GAAG,CAAC,EACzF,CAAC,GAAoB,EAAsB,CAC5C,EAEK,IACJ,EAAgB,SAAS,KACzB,EAAgB,OAAO,MAAW,EAAkB,IAAI,EAAO,GAAG,CAAC,EAC/D,KACJ,EAAgB,MAAM,MAAW,EAAkB,IAAI,EAAO,GAAG,CAAC,IAClE,CAAC,GAEG,IACJ,EAAmB,SAAS,KAC5B,EAAmB,OAAO,MAAe,EAAsB,IAAI,EAAW,GAAG,CAAC,EAC9E,IACJ,EAAmB,MAAM,MAAe,EAAsB,IAAI,EAAW,GAAG,CAAC,IACjF,CAAC;AAGH,eACe,GAAsB,EAClC,CAAC,EAAqB,CAAC;CAK1B,IAAM,CAAC,GAAc,KAAmB,IAAiB;AACzD,SAAgB;AACd,MAAI,EAAa,IAAI,SAAS,KAAK,UAAU,GAAa;AACxD,KAAuB,EAAE,IAAI,GAAa,CAAc;GACxD,IAAM,IAAO,IAAI,gBAAgB,EAAa;AAE9C,GADA,EAAK,OAAO,SAAS,EACrB,EAAgB,GAAM,EAAE,SAAS,IAAM,CAAC;;IAEzC;EAAC;EAAc;EAAiB;EAAa;EAAuB,CAAC;CAExE,IAAM,EACJ,oBACA,oBACA,oBACA,WAAW,OACT,IAAyB;AAE7B,KAAI,GACF,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,GAAD,EAAU,WAAU,cAAe,CAAA,EACnC,kBAAC,GAAD,EAAU,WAAU,QAAS,CAAA,CACzB;;AAIV,KAAI,CAAC,MAAkB,CAAC,EACtB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cACX,EAAc,GAAG,4BAA4B,gBAAgB;IAC3D,CAAA,EACL,kBAAC,KAAD;IAAG,WAAU;cACV,EACC,GACA,yCACA,uDACD;IACC,CAAA,CACA;;EACF,CAAA;CAIV,IAAM,UAA2B;AAC/B,EAAI,KACF,EAAuB,EAAE,IAAI,GAAa,CAAc;IAItD,MAAoB,MAAqB;AAC7C,IAAgB,SAAS,MAAW;AAElC,GAAI,MADe,EAAkB,IAAI,EAAO,GAAG,IAEjD,EAAsB,EAAO,GAAG;IAElC;IAGE,MAA6B,GAAsB,MAAqB;AAC5E,KAA0B,MAAS;GACjC,IAAM,IAAO,IAAI,IAAI,EAAK;AAM1B,UALI,IACF,EAAK,IAAI,EAAa,GAEtB,EAAK,OAAO,EAAa,EAEpB;IACP;IAGE,MAAwB,MAAqB;AACjD,KAA0B,MAAS;GACjC,IAAM,IAAO,IAAI,IAAI,EAAK;AAQ1B,UAPA,EAAmB,SAAS,MAAe;AACzC,IAAI,IACF,EAAK,IAAI,EAAW,GAAG,GAEvB,EAAK,OAAO,EAAW,GAAG;KAE5B,EACK;IACP;IAGE,KAA0B,YAAY;AAC1C,MAAI,EAAgB,WAAW,EAAG;AAC7B,IAAa,SAAS;EAC3B,IAAM,IAAU,GAAG,EAAc,GAAG,uCAAuC,SAAS,CAAC,GAAG,EAAgB,OAAO,GAC7G,EAAgB,WAAW,IACvB,EAAc,GAAG,yCAAyC,kBAAkB,GAC5E,EAAc,GAAG,uCAAuC,mBAAmB,CAChF,GAAG,EAAc,GAAG,uCAAuC,uBAAuB,IAC7E,IAAS,MAAM,EAAc;GACjC,OAAO;GACP;GACA,eAAe;GACf,mBAAmB;GACpB,CAAC;AACgB,SAAW,OAAO,OAAO,QAAQ,EAAQ,GAAG,OAG9D,EAAgB,SAAS,MAAW;AAClC,MAAwB,EAAO,OAAO;IACtC,EACF,GAAsB;IAGlB,WAAoC;AACpC,IAAoB,WAAW,KACnC,EAAoB,SAAS,MAAe;AAC1C,KAAiB,EAAW,IAAI,EAC9B,iBAAiB;AACf,OAA0B,MAAS;KACjC,IAAM,IAAO,IAAI,IAAI,EAAK;AAE1B,YADA,EAAK,OAAO,EAAW,GAAG,EACnB;MACP;MAEL,CAAC;IACF;;AAGJ,QACE,kBAAC,IAAD;EAAe,QAAQ;YAAvB,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;MAAI,WAAU;gBACX,EAAc,GAAG,yBAAyB,UAAU;MAClD,CAAA,EACL,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,GACA,4BACA,2CACD;MACC,CAAA,CACA,EAAA,CAAA,EAEN,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,oCACA,8CACD;iBAED,kBAAC,GAAD;QAAQ,SAAS;QAAoB,UAAU,CAAC;kBAAhD,CACE,kBAAC,GAAD,EAAU,WAAU,kBAAmB,CAAA,EACvC,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAc,GAAG,4BAA4B,gBAAgB;SACzD,CAAA,CACA;;OACQ,CAAA;MACf,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,yEAA0E,CAAA,EAC5F,kBAAC,IAAD;OACE,aAAa,EAAc,GAAG,iCAAiC,oBAAoB;OACnF,OAAO;OACP,WAAW,MAAM,GAAe,EAAE,OAAO,MAAM;OAC/C,WAAU;OACV,CAAA,CACE;SAEL,KACC,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAO,SAAQ;OAAY,WAAU;iBAArC;QACG,EAAmB;QAAQ;QAC3B,EAAmB,WAAW,IAE3B,EAAc,GAAG,iCAAiC,qBAAqB,GADvE,EAAc,GAAG,kCAAkC,sBAAsB;QAEvE;;MACJ,CAAA,CAEJ;;IAEL,EAAgB,SAAS,KACxB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,SAAD;MAAO,WAAU;gBAAjB,CACE,kBAAC,SAAD;OACE,MAAK;OACL,SAAS;OACT,MAAM,MAAU;AACd,QAAI,MACF,EAAM,gBAAgB,MAA4B,CAAC;;OAGvD,WAAW,MAAU,GAAiB,EAAM,OAAO,QAAQ;OAC3D,cAAY,EACV,GACA,yCACA,+BACD;OACD,WAAU;OACV,CAAA,EACF,kBAAC,QAAD,EAAA,UACG,EACC,GACA,uCACA,6BACD,EACI,CAAA,CACD;SAER,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAkB;SAAK;SAAE,EAAc,GAAG,wBAAwB,WAAW;SACzE;;OACN,EAAkB,OAAO,KACxB,kBAAC,GAAD;QAAQ,SAAQ;QAAQ,MAAK;QAAK,SAAS;kBACxC,EAAc,GAAG,8BAA8B,kBAAkB;QAC3D,CAAA;OAEV,KACC,kBAAC,GAAD;QACE,SAAQ;QACR,MAAK;QACL,SAAS;QACT,UAAU,EAAgB,WAAW,KAAK;QAC1C,WAAU;kBALZ,CAOG,EAAc,GAAG,8BAA8B,kBAAkB,EACjE,EAAgB,SAAS,IAAI,KAAK,EAAgB,OAAO,KAAK,GACxD;;OAEP;QACF;;IAIP,KACC,kBAAC,OAAD;KAAK,WAAU;eACZ,CAAC,GAAG;;;;;;MAAQ,CAAC,CAAC,KAAK,GAAG,MACrB,kBAAC,GAAD,EAAkB,WAAU,eAAgB,EAA7B,EAA6B,CAC5C;KACE,CAAA;IAIP,CAAC,KAAa,KACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,IAAD;QAAa,MAAM;QAAI,WAAU;QAAqB,CAAA;OAClD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,4BAA4B,yBAAyB;OACpE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,aAAwB,QACrB,EAAa,UACb,EACE,GACA,mCACA,0CACD;OACH,CAAA;MACA;;IAIP,CAAC,KAAa,CAAC,KAAgB,CAAC,KAC/B,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAO,MAAM;QAAI,WAAU;QAA0B,CAAA;OACjD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,mCAAmC,wBAAwB;OAC1E,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,GACA,qCACA,oDACD;OACC,CAAA;MACA;;IAIP,CAAC,KAAa,CAAC,KAAgB,KAAe,CAAC,KAC9C,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAO,MAAM;QAAI,WAAU;QAA0B,CAAA;OACjD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,4BAA4B,iBAAiB;OAC5D,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,GACA,+BACA,iEACD;OACC,CAAA;MACJ,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,oCACA,8CACD;iBAED,kBAAC,GAAD;QAAQ,SAAS;kBAAjB,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EACnC,EAAc,GAAG,iCAAiC,2BAA2B,CACvE;;OACQ,CAAA;MACf;;IAIP,CAAC,KACA,CAAC,KACD,KACA,KACA,EAAgB,WAAW,KACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAQ,MAAM;QAAI,WAAU;QAA0B,CAAA;OAClD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,8BAA8B,mBAAmB;OAChE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,GACA,oCACA,oEACD;OACC,CAAA;MACA;;IAIT,CAAC,KAAa,EAAgB,SAAS,KACtC,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,MAAD;OAAI,WAAU;iBAAd;QACG,EAAc,GAAG,6BAA6B,iBAAiB;QAAC;QAChE,EAAgB;QAAO;QACrB;;MACD,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAgB,KAAK,MACpB,kBAAC,IAAD;OAEU;OACR,UAAU,EAAkB,IAAI,EAAO,GAAG;OAC1C,iBAAiB,MAAY;AAE3B,QAAI,MADe,EAAkB,IAAI,EAAO,GAAG,IAEjD,EAAsB,EAAO,GAAG;;OAGpC,WAAW;OACX,EAVK,EAAO,GAUZ,CACF;MACE,CAAA,CACF;;IAIP,CAAC,KAAa,KACb,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,wCAAwC,sBAAsB;OAC7E,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,SAAD;SACE,MAAK;SACL,SAAS;SACT,MAAM,MAAU;AACd,UAAI,MACF,EAAM,gBACJ,KAAgC,CAAC;;SAGvC,WAAW,MAAU,GAAqB,EAAM,OAAO,QAAQ;SAC/D,cAAY,EACV,GACA,6CACA,iCACD;SACD,WAAU;SACV,CAAA,EACF,kBAAC,QAAD,EAAA,UACG,EAAc,GAAG,oCAAoC,yBAAyB,EAC1E,CAAA,CACD;WAER,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,QAAD;UAAM,WAAU;oBAAhB;WACG,EAAsB;WAAM;WAC5B,EAAc,GAAG,wBAAwB,WAAW;WAChD;;SACN,EAAsB,OAAO,KAC5B,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,eAAe,kBAAyB,IAAI,KAAK,CAAC;oBAEjD,EAAc,GAAG,8BAA8B,kBAAkB;UAC3D,CAAA;SAEV,KACC,kBAAC,GAAD;UACE,SAAQ;UACR,MAAK;UACL,SAAS;UACT,UAAU,EAAoB,WAAW,KAAK;oBAJhD,CAMG,EAAc,GAAG,8BAA8B,kBAAkB,EACjE,EAAoB,SAAS,IAAI,KAAK,EAAoB,OAAO,KAAK,GAChE;;SAEP;UACF;;MACN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAmB,KAAK,MACvB,kBAAC,OAAD;QAEE,WAAU;kBAFZ,CAIE,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,SAAD;WACE,MAAK;WACL,SAAS,EAAsB,IAAI,EAAW,GAAG;WACjD,WAAW,MACT,GAA0B,EAAW,IAAI,EAAM,OAAO,QAAQ;WAEhE,cAAY,GAAG,EAAc,GAAG,yCAAyC,oBAAoB,CAAC,GAAG,EAAW;WAC5G,WAAU;WACV,CAAA;UACF,kBAAC,OAAD;WAAK,WAAU;qBACb,kBAAC,GAAD,EAAO,WAAU,8BAA+B,CAAA;WAC5C,CAAA;UACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;WAAG,WAAU;qBAAuB,EAAW;WAAU,CAAA,EACzD,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAc,GAAG,uBAAuB,UAAU;YAAE;YACpD,IAAI,KAAK,EAAW,UAAU,CAAC,oBAAoB;YAClD;aACA,EAAA,CAAA;UACF;YACN,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UACE,YAAY,EAAsB;UAClC,gBAAe;UACf,eAAe,EACb,GACA,oCACA,kDACD;oBAED,kBAAC,GAAD;WACE,SAAQ;WACR,MAAK;WACL,eAAe,GAAuB,EAAW,GAAG;WACpD,UAAU,KAAe,MAAgB,EAAW;qBAJtD,CAME,kBAAC,IAAD,EACE,WAAW,eAAe,KAAe,MAAgB,EAAW,KAAK,iBAAiB,MAC1F,CAAA,EACD,KAAe,MAAgB,EAAW,KACvC,EAAc,GAAG,uBAAuB,aAAa,GACrD,EAAc,GAAG,sBAAsB,SAAS,CAC7C;;UACQ,CAAA,EACnB,kBAAC,GAAD;UACE,SAAQ;UACR,WAAU;oBAET,EAAc,GAAG,uBAAuB,UAAU;UAC7C,CAAA,CACJ;WACF;UAvDC,EAAW,GAuDZ,CACN;OACE,CAAA;MACF;;IAEJ;MAGN,kBAAC,IAAD,EAAiC,gBAAe,CAAA,CAClC"}
|
|
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 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-muted-foreground\">\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-muted-foreground 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-muted-foreground\" />\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 bg-card p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\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 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-muted-foreground\">\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-destructive\"\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-destructive/10 p-6 mb-4\">\n <AlertCircle size={48} className=\"text-destructive\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Users size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Users size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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-muted p-6 mb-4\">\n <Search size={48} className=\"text-muted-foreground\" />\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-muted-foreground 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 bg-card p-4 sm:flex-row sm:items-center sm:justify-between\">\n <label className=\"flex items-center gap-2 text-sm text-foreground\">\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 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-muted-foreground\">\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-card\"\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 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-muted-foreground\">\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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"}
|
|
@@ -3,15 +3,16 @@ import { useMyPendingInvitations as t } from "../hooks/usePendingInvitations.js"
|
|
|
3
3
|
import { tWithFallback as n } from "../utils/i18n.js";
|
|
4
4
|
import { useWorkspacesPageView as r } from "../hooks/useWorkspacesPageView.js";
|
|
5
5
|
import { useAcceptInvitation as i, useRejectInvitation as a } from "../hooks/useMemberMutations.js";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
6
|
+
import { getProjectDisplayNames as o, getRoleDisplayNames as s, getWorkspaceDisplayName as c } from "../utils/invitationDisplay.js";
|
|
7
|
+
import { useMemo as l, useState as u } from "react";
|
|
8
|
+
import { AlertTriangle as d, CheckCircle as f, Home as p, Inbox as m, Mail as h, RefreshCw as g, XCircle as _ } from "lucide-react";
|
|
9
|
+
import { jsx as v, jsxs as y } from "react/jsx-runtime";
|
|
10
|
+
import { Badge as b, Button as x, PageContainer as S, Skeleton as C } from "@burdenoff/fe-libs/ui";
|
|
11
|
+
import { useNavigate as w } from "react-router-dom";
|
|
12
|
+
import { useI18n as T } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
13
|
+
import { ActorIdentity as E, useActorProfiles as D } from "@burdenoff/fe-libs/shared";
|
|
13
14
|
//#region src/pages/PendingInvitesPage.tsx
|
|
14
|
-
function
|
|
15
|
+
function O(e) {
|
|
15
16
|
if (!e) return null;
|
|
16
17
|
let t = new Date(e);
|
|
17
18
|
return Number.isNaN(t.getTime()) ? null : t.toLocaleDateString(void 0, {
|
|
@@ -22,222 +23,225 @@ function T(e) {
|
|
|
22
23
|
minute: "2-digit"
|
|
23
24
|
});
|
|
24
25
|
}
|
|
25
|
-
function
|
|
26
|
-
if (e.workspaceName) return e.workspaceName;
|
|
27
|
-
let t = e.context?.metadata?.workspaceName;
|
|
28
|
-
return typeof t == "string" && t.trim().length > 0 ? t : e.workspaceId || "—";
|
|
29
|
-
}
|
|
30
|
-
function D(e) {
|
|
31
|
-
if (Array.isArray(e.roleNames) && e.roleNames.length > 0) return e.roleNames;
|
|
32
|
-
let t = e.context?.metadata?.roleNames;
|
|
33
|
-
return Array.isArray(t) && t.every((e) => typeof e == "string") ? t : e.roleIds;
|
|
34
|
-
}
|
|
35
|
-
function O() {
|
|
26
|
+
function k() {
|
|
36
27
|
r("workspaces.pending-invites");
|
|
37
|
-
let { t:
|
|
28
|
+
let { t: k } = T(), A = w(), { currentUser: j, authToken: M, apiGatewayUrl: N, organizationId: P, basePath: F = "/workspaces" } = e(), [I, L] = u(null), [R, z] = u(null), { data: B = [], isLoading: V, error: H } = t(j?.email), U = l(() => [...B].sort((e, t) => new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime()), [B]), W = D(l(() => U.flatMap((e) => e.invitedById ? [{
|
|
38
29
|
actorId: e.invitedById,
|
|
39
30
|
actorType: "user"
|
|
40
|
-
}] : []), [
|
|
41
|
-
apiGatewayUrl:
|
|
42
|
-
authToken:
|
|
43
|
-
orgId:
|
|
44
|
-
}), { mutate:
|
|
45
|
-
|
|
46
|
-
},
|
|
47
|
-
|
|
31
|
+
}] : []), [U]), {
|
|
32
|
+
apiGatewayUrl: N,
|
|
33
|
+
authToken: M,
|
|
34
|
+
orgId: P
|
|
35
|
+
}), { mutate: G, isPending: K } = i(), { mutate: q, isPending: J } = a(), Y = K || J, X = () => {
|
|
36
|
+
A(`${F}/list`);
|
|
37
|
+
}, Z = (e) => {
|
|
38
|
+
L(e), z(null), G(e, {
|
|
48
39
|
onSuccess: () => {
|
|
49
|
-
|
|
40
|
+
L(null);
|
|
50
41
|
},
|
|
51
42
|
onError: (e) => {
|
|
52
|
-
|
|
43
|
+
L(null), z(e instanceof Error ? e.message : n(k, "pendingInvites.acceptError", "Failed to accept invitation"));
|
|
53
44
|
}
|
|
54
45
|
});
|
|
55
|
-
},
|
|
56
|
-
|
|
46
|
+
}, Q = (e) => {
|
|
47
|
+
L(e), z(null), q(e, {
|
|
57
48
|
onSuccess: () => {
|
|
58
|
-
|
|
49
|
+
L(null);
|
|
59
50
|
},
|
|
60
51
|
onError: (e) => {
|
|
61
|
-
|
|
52
|
+
L(null), z(e instanceof Error ? e.message : n(k, "pendingInvites.rejectError", "Failed to reject invitation"));
|
|
62
53
|
}
|
|
63
54
|
});
|
|
64
55
|
};
|
|
65
|
-
return !
|
|
56
|
+
return !M || !j?.email ? /* @__PURE__ */ v(S, {
|
|
66
57
|
padded: !1,
|
|
67
|
-
children: /* @__PURE__ */
|
|
58
|
+
children: /* @__PURE__ */ v("div", {
|
|
68
59
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
69
|
-
children: /* @__PURE__ */
|
|
60
|
+
children: /* @__PURE__ */ y("div", {
|
|
70
61
|
className: "rounded-lg border bg-card p-6 text-center",
|
|
71
62
|
children: [
|
|
72
|
-
/* @__PURE__ */
|
|
73
|
-
/* @__PURE__ */
|
|
63
|
+
/* @__PURE__ */ v(d, { className: "mx-auto mb-3 size-10 text-muted-foreground" }),
|
|
64
|
+
/* @__PURE__ */ v("h1", {
|
|
74
65
|
className: "text-2xl font-bold",
|
|
75
|
-
children: n(
|
|
66
|
+
children: n(k, "pendingInvites.signInTitle", "Sign in to view your pending invites")
|
|
76
67
|
}),
|
|
77
|
-
/* @__PURE__ */
|
|
68
|
+
/* @__PURE__ */ v("p", {
|
|
78
69
|
className: "mt-2 text-muted-foreground",
|
|
79
|
-
children: n(
|
|
70
|
+
children: n(k, "pendingInvites.signInDescription", "You need an authenticated account before you can review or respond to workspace invitations.")
|
|
80
71
|
}),
|
|
81
|
-
/* @__PURE__ */
|
|
72
|
+
/* @__PURE__ */ v("div", {
|
|
82
73
|
className: "mt-4 flex justify-center",
|
|
83
|
-
children: /* @__PURE__ */
|
|
84
|
-
onClick:
|
|
74
|
+
children: /* @__PURE__ */ y(x, {
|
|
75
|
+
onClick: X,
|
|
85
76
|
variant: "outline",
|
|
86
|
-
children: [/* @__PURE__ */
|
|
77
|
+
children: [/* @__PURE__ */ v(p, { className: "mr-2 size-4" }), n(k, "pendingInvites.backToWorkspaces", "Back to Workspaces")]
|
|
87
78
|
})
|
|
88
79
|
})
|
|
89
80
|
]
|
|
90
81
|
})
|
|
91
82
|
})
|
|
92
|
-
}) : /* @__PURE__ */
|
|
83
|
+
}) : /* @__PURE__ */ v(S, {
|
|
93
84
|
padded: !1,
|
|
94
|
-
children: /* @__PURE__ */
|
|
85
|
+
children: /* @__PURE__ */ y("div", {
|
|
95
86
|
className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
|
|
96
87
|
children: [
|
|
97
|
-
/* @__PURE__ */
|
|
88
|
+
/* @__PURE__ */ y("div", {
|
|
98
89
|
className: "flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between",
|
|
99
|
-
children: [/* @__PURE__ */
|
|
90
|
+
children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
|
|
100
91
|
className: "text-2xl sm:text-3xl font-bold",
|
|
101
|
-
children: n(
|
|
102
|
-
}), /* @__PURE__ */
|
|
92
|
+
children: n(k, "pendingInvites.pageTitle", "Pending Invites")
|
|
93
|
+
}), /* @__PURE__ */ v("p", {
|
|
103
94
|
className: "text-sm sm:text-base text-muted-foreground",
|
|
104
|
-
children: n(
|
|
105
|
-
})] }), /* @__PURE__ */
|
|
95
|
+
children: n(k, "pendingInvites.pageSubtitle", "Review and respond to workspace invitations sent to your account.")
|
|
96
|
+
})] }), /* @__PURE__ */ y("div", {
|
|
106
97
|
className: "flex items-center gap-2",
|
|
107
|
-
children: [/* @__PURE__ */
|
|
98
|
+
children: [/* @__PURE__ */ v(b, {
|
|
108
99
|
variant: "secondary",
|
|
109
|
-
children:
|
|
110
|
-
}), /* @__PURE__ */
|
|
100
|
+
children: U.length
|
|
101
|
+
}), /* @__PURE__ */ y(x, {
|
|
111
102
|
variant: "outline",
|
|
112
|
-
onClick:
|
|
113
|
-
children: [/* @__PURE__ */
|
|
103
|
+
onClick: X,
|
|
104
|
+
children: [/* @__PURE__ */ v(p, { className: "mr-2 size-4" }), n(k, "pendingInvites.viewWorkspaces", "View Workspaces")]
|
|
114
105
|
})]
|
|
115
106
|
})]
|
|
116
107
|
}),
|
|
117
|
-
|
|
108
|
+
R ? /* @__PURE__ */ y("div", {
|
|
118
109
|
className: "flex items-start gap-2 rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
110
|
+
children: [/* @__PURE__ */ v(d, { className: "mt-0.5 size-4 shrink-0" }), /* @__PURE__ */ v("span", { children: R })]
|
|
120
111
|
}) : null,
|
|
121
|
-
|
|
112
|
+
H ? /* @__PURE__ */ v("div", {
|
|
122
113
|
className: "rounded-lg border border-destructive/30 bg-destructive/10 p-4 text-sm text-destructive",
|
|
123
|
-
children:
|
|
114
|
+
children: H instanceof Error ? H.message : n(k, "pendingInvites.loadError", "Failed to load pending invitations")
|
|
124
115
|
}) : null,
|
|
125
|
-
|
|
116
|
+
V ? /* @__PURE__ */ v("div", {
|
|
126
117
|
className: "space-y-4",
|
|
127
|
-
children: Array.from({ length: 3 }).map((e, t) => /* @__PURE__ */
|
|
118
|
+
children: Array.from({ length: 3 }).map((e, t) => /* @__PURE__ */ y("div", {
|
|
128
119
|
className: "rounded-lg border bg-card p-5 space-y-3",
|
|
129
120
|
children: [
|
|
130
|
-
/* @__PURE__ */
|
|
131
|
-
/* @__PURE__ */
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
/* @__PURE__ */
|
|
121
|
+
/* @__PURE__ */ v(C, { className: "h-5 w-40" }),
|
|
122
|
+
/* @__PURE__ */ v(C, { className: "h-4 w-64" }),
|
|
123
|
+
/* @__PURE__ */ v(C, { className: "h-4 w-52" }),
|
|
124
|
+
/* @__PURE__ */ y("div", {
|
|
134
125
|
className: "flex gap-2 pt-2",
|
|
135
|
-
children: [/* @__PURE__ */
|
|
126
|
+
children: [/* @__PURE__ */ v(C, { className: "h-10 w-36" }), /* @__PURE__ */ v(C, { className: "h-10 w-28" })]
|
|
136
127
|
})
|
|
137
128
|
]
|
|
138
129
|
}, t))
|
|
139
|
-
}) :
|
|
130
|
+
}) : U.length === 0 ? /* @__PURE__ */ y("div", {
|
|
140
131
|
className: "rounded-lg border bg-card p-10 text-center",
|
|
141
132
|
children: [
|
|
142
|
-
/* @__PURE__ */
|
|
143
|
-
/* @__PURE__ */
|
|
133
|
+
/* @__PURE__ */ v(m, { className: "mx-auto mb-4 size-12 text-muted-foreground" }),
|
|
134
|
+
/* @__PURE__ */ v("h2", {
|
|
144
135
|
className: "text-xl font-semibold",
|
|
145
|
-
children: n(
|
|
136
|
+
children: n(k, "pendingInvites.emptyTitle", "No pending invites")
|
|
146
137
|
}),
|
|
147
|
-
/* @__PURE__ */
|
|
138
|
+
/* @__PURE__ */ v("p", {
|
|
148
139
|
className: "mt-2 text-muted-foreground",
|
|
149
|
-
children: n(
|
|
140
|
+
children: n(k, "pendingInvites.emptyDescription", "When someone invites you to a workspace, it will appear here.")
|
|
150
141
|
})
|
|
151
142
|
]
|
|
152
|
-
}) : /* @__PURE__ */
|
|
143
|
+
}) : /* @__PURE__ */ v("div", {
|
|
153
144
|
className: "space-y-4",
|
|
154
|
-
children:
|
|
155
|
-
let t = e.invitedById ?
|
|
156
|
-
return /* @__PURE__ */
|
|
145
|
+
children: U.map((e) => {
|
|
146
|
+
let t = e.invitedById ? W[e.invitedById] : void 0, r = t?.displayName || t?.email || e.invitedById || "—", i = O(e.expiresAt), a = O(e.createdAt), l = c(e), u = s(e), d = o(e), p = I === e.id && Y;
|
|
147
|
+
return /* @__PURE__ */ v("div", {
|
|
157
148
|
className: "rounded-lg border bg-card p-5",
|
|
158
|
-
children: /* @__PURE__ */
|
|
149
|
+
children: /* @__PURE__ */ y("div", {
|
|
159
150
|
className: "flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between",
|
|
160
|
-
children: [/* @__PURE__ */
|
|
151
|
+
children: [/* @__PURE__ */ y("div", {
|
|
161
152
|
className: "space-y-3 min-w-0",
|
|
162
153
|
children: [
|
|
163
|
-
/* @__PURE__ */
|
|
154
|
+
/* @__PURE__ */ y("div", {
|
|
164
155
|
className: "flex flex-wrap items-center gap-2",
|
|
165
|
-
children: [/* @__PURE__ */
|
|
156
|
+
children: [/* @__PURE__ */ y("div", {
|
|
166
157
|
className: "flex items-center gap-2 font-semibold text-lg",
|
|
167
|
-
children: [/* @__PURE__ */ h
|
|
168
|
-
}), /* @__PURE__ */
|
|
158
|
+
children: [/* @__PURE__ */ v(h, { className: "size-4 text-primary" }), /* @__PURE__ */ v("span", { children: e.email })]
|
|
159
|
+
}), /* @__PURE__ */ v(b, {
|
|
169
160
|
variant: "secondary",
|
|
170
161
|
children: e.status
|
|
171
162
|
})]
|
|
172
163
|
}),
|
|
173
|
-
/* @__PURE__ */
|
|
164
|
+
/* @__PURE__ */ y("dl", {
|
|
174
165
|
className: "grid gap-2 text-sm text-muted-foreground sm:grid-cols-2",
|
|
175
166
|
children: [
|
|
176
|
-
/* @__PURE__ */
|
|
177
|
-
/* @__PURE__ */
|
|
167
|
+
/* @__PURE__ */ y("div", { children: [
|
|
168
|
+
/* @__PURE__ */ v("dt", {
|
|
178
169
|
className: "font-medium text-foreground",
|
|
179
|
-
children: n(
|
|
170
|
+
children: n(k, "pendingInvites.workspaceLabel", "Workspace")
|
|
180
171
|
}),
|
|
181
|
-
/* @__PURE__ */
|
|
172
|
+
/* @__PURE__ */ v("dd", {
|
|
182
173
|
className: "break-words font-medium text-foreground",
|
|
183
|
-
children:
|
|
174
|
+
children: l
|
|
184
175
|
}),
|
|
185
|
-
e.workspaceId ? /* @__PURE__ */
|
|
176
|
+
e.workspaceId ? /* @__PURE__ */ v("dd", {
|
|
186
177
|
className: "mt-1 break-all font-mono text-xs text-muted-foreground",
|
|
187
178
|
children: e.workspaceId
|
|
188
179
|
}) : null
|
|
189
180
|
] }),
|
|
190
|
-
/* @__PURE__ */
|
|
181
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("dt", {
|
|
191
182
|
className: "font-medium text-foreground",
|
|
192
|
-
children: n(
|
|
193
|
-
}), /* @__PURE__ */
|
|
183
|
+
children: n(k, "pendingInvites.invitedByLabel", "Invited by")
|
|
184
|
+
}), /* @__PURE__ */ v("dd", {
|
|
194
185
|
className: "break-all",
|
|
195
|
-
children: t ? /* @__PURE__ */
|
|
186
|
+
children: t ? /* @__PURE__ */ v(E, {
|
|
196
187
|
profile: t,
|
|
197
188
|
variant: "inline",
|
|
198
189
|
hideIdDrilldown: !0
|
|
199
|
-
}) : /* @__PURE__ */
|
|
190
|
+
}) : /* @__PURE__ */ v("span", {
|
|
200
191
|
className: "font-medium text-foreground",
|
|
201
192
|
children: r
|
|
202
193
|
})
|
|
203
194
|
})] }),
|
|
204
|
-
/* @__PURE__ */
|
|
195
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("dt", {
|
|
205
196
|
className: "font-medium text-foreground",
|
|
206
|
-
children: n(
|
|
207
|
-
}), /* @__PURE__ */
|
|
208
|
-
/* @__PURE__ */
|
|
197
|
+
children: n(k, "pendingInvites.receivedLabel", "Received")
|
|
198
|
+
}), /* @__PURE__ */ v("dd", { children: a ?? "—" })] }),
|
|
199
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("dt", {
|
|
209
200
|
className: "font-medium text-foreground",
|
|
210
|
-
children: n(
|
|
211
|
-
}), /* @__PURE__ */
|
|
201
|
+
children: n(k, "pendingInvites.expiresLabel", "Expires")
|
|
202
|
+
}), /* @__PURE__ */ v("dd", { children: i ?? "—" })] })
|
|
212
203
|
]
|
|
213
204
|
}),
|
|
214
|
-
/* @__PURE__ */
|
|
205
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
215
206
|
className: "text-sm font-medium text-foreground mb-2",
|
|
216
|
-
children: n(
|
|
217
|
-
}), /* @__PURE__ */
|
|
207
|
+
children: n(k, "pendingInvites.rolesLabel", "Roles")
|
|
208
|
+
}), /* @__PURE__ */ v("div", {
|
|
218
209
|
className: "flex flex-wrap gap-2",
|
|
219
|
-
children:
|
|
210
|
+
children: u.length > 0 ? u.map((t, n) => /* @__PURE__ */ v(b, {
|
|
220
211
|
variant: "outline",
|
|
221
212
|
children: t
|
|
222
|
-
}, `${e.id}-${e.roleIds[n] ?? t}`)) : /* @__PURE__ */
|
|
213
|
+
}, `${e.id}-${e.roleIds[n] ?? t}`)) : /* @__PURE__ */ v("span", {
|
|
214
|
+
className: "text-sm text-muted-foreground",
|
|
215
|
+
children: n(k, "pendingInvites.noRoles", "No explicit roles assigned")
|
|
216
|
+
})
|
|
217
|
+
})] }),
|
|
218
|
+
/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("p", {
|
|
219
|
+
className: "text-sm font-medium text-foreground mb-2",
|
|
220
|
+
children: n(k, "pendingInvites.projectsLabel", "Projects")
|
|
221
|
+
}), /* @__PURE__ */ v("div", {
|
|
222
|
+
className: "flex flex-wrap gap-2",
|
|
223
|
+
children: d.length > 0 ? d.map((t, n) => /* @__PURE__ */ v(b, {
|
|
224
|
+
variant: "secondary",
|
|
225
|
+
children: t
|
|
226
|
+
}, `${e.id}-project-${t}-${n}`)) : /* @__PURE__ */ v("span", {
|
|
223
227
|
className: "text-sm text-muted-foreground",
|
|
224
|
-
children: n(
|
|
228
|
+
children: n(k, "pendingInvites.noProjects", "No project access will be granted automatically")
|
|
225
229
|
})
|
|
226
230
|
})] })
|
|
227
231
|
]
|
|
228
|
-
}), /* @__PURE__ */
|
|
232
|
+
}), /* @__PURE__ */ y("div", {
|
|
229
233
|
className: "flex flex-col gap-2 sm:flex-row lg:flex-col lg:min-w-44",
|
|
230
|
-
children: [/* @__PURE__ */
|
|
231
|
-
onClick: () =>
|
|
232
|
-
disabled:
|
|
234
|
+
children: [/* @__PURE__ */ y(x, {
|
|
235
|
+
onClick: () => Z(e.id),
|
|
236
|
+
disabled: Y,
|
|
233
237
|
className: "w-full",
|
|
234
|
-
children: [
|
|
235
|
-
}), /* @__PURE__ */
|
|
238
|
+
children: [p && K ? /* @__PURE__ */ v(g, { className: "mr-2 size-4 animate-spin" }) : /* @__PURE__ */ v(f, { className: "mr-2 size-4" }), n(k, "pendingInvites.acceptButton", "Accept")]
|
|
239
|
+
}), /* @__PURE__ */ y(x, {
|
|
236
240
|
variant: "outline",
|
|
237
|
-
onClick: () =>
|
|
238
|
-
disabled:
|
|
241
|
+
onClick: () => Q(e.id),
|
|
242
|
+
disabled: Y,
|
|
239
243
|
className: "w-full",
|
|
240
|
-
children: [
|
|
244
|
+
children: [p && J ? /* @__PURE__ */ v(g, { className: "mr-2 size-4 animate-spin" }) : /* @__PURE__ */ v(_, { className: "mr-2 size-4" }), n(k, "pendingInvites.rejectButton", "Reject")]
|
|
241
245
|
})]
|
|
242
246
|
})]
|
|
243
247
|
})
|
|
@@ -249,6 +253,6 @@ function O() {
|
|
|
249
253
|
});
|
|
250
254
|
}
|
|
251
255
|
//#endregion
|
|
252
|
-
export {
|
|
256
|
+
export { k as PendingInvitesPage };
|
|
253
257
|
|
|
254
258
|
//# sourceMappingURL=PendingInvitesPage.js.map
|