@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.
Files changed (44) hide show
  1. package/dist/WorkspacesRoutes.js +36 -17
  2. package/dist/WorkspacesRoutes.js.map +1 -1
  3. package/dist/components/InviteMemberDialog.js +204 -117
  4. package/dist/components/InviteMemberDialog.js.map +1 -1
  5. package/dist/components/MemberListItem.js +2 -1
  6. package/dist/components/MemberListItem.js.map +1 -1
  7. package/dist/components/ProjectMemberPickerDialog.js +211 -0
  8. package/dist/components/ProjectMemberPickerDialog.js.map +1 -0
  9. package/dist/hooks/index.js +1 -1
  10. package/dist/hooks/useMemberMutations.js +13 -13
  11. package/dist/hooks/useMemberMutations.js.map +1 -1
  12. package/dist/hooks/useProjectMemberMutations.js +104 -0
  13. package/dist/hooks/useProjectMemberMutations.js.map +1 -0
  14. package/dist/hooks/useProjectMembers.js +42 -0
  15. package/dist/hooks/useProjectMembers.js.map +1 -0
  16. package/dist/hooks/useWorkspaceMembers.js +60 -24
  17. package/dist/hooks/useWorkspaceMembers.js.map +1 -1
  18. package/dist/hooks/useWorkspacePermissions.js +4 -0
  19. package/dist/hooks/useWorkspacePermissions.js.map +1 -1
  20. package/dist/hooks/useWorkspacesEventEmitter.js.map +1 -1
  21. package/dist/index.js +16 -15
  22. package/dist/pages/ActivityListPage.js +5 -5
  23. package/dist/pages/InviteAcceptancePage.js +211 -207
  24. package/dist/pages/InviteAcceptancePage.js.map +1 -1
  25. package/dist/pages/MembersListPage.js +210 -208
  26. package/dist/pages/MembersListPage.js.map +1 -1
  27. package/dist/pages/PendingInvitesPage.js +124 -120
  28. package/dist/pages/PendingInvitesPage.js.map +1 -1
  29. package/dist/pages/ProjectDetailPage.js +249 -213
  30. package/dist/pages/ProjectDetailPage.js.map +1 -1
  31. package/dist/pages/ProjectMembersPage.js +265 -0
  32. package/dist/pages/ProjectMembersPage.js.map +1 -0
  33. package/dist/pages/ProjectsListPage.js +9 -9
  34. package/dist/pages/WorkspaceDetailPage.js +119 -111
  35. package/dist/pages/WorkspaceDetailPage.js.map +1 -1
  36. package/dist/pages/WorkspaceInvitePage.js +790 -0
  37. package/dist/pages/WorkspaceInvitePage.js.map +1 -0
  38. package/dist/pages/WorkspacesListPage.js +115 -100
  39. package/dist/pages/WorkspacesListPage.js.map +1 -1
  40. package/dist/utils/invitationDisplay.js +25 -0
  41. package/dist/utils/invitationDisplay.js.map +1 -0
  42. package/dist/utils/workspaceBulkInviteCsv.js +42 -0
  43. package/dist/utils/workspaceBulkInviteCsv.js.map +1 -0
  44. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkspaceInvitePage.js","names":[],"sources":["../../src/pages/WorkspaceInvitePage.tsx"],"sourcesContent":["import { useMemo, useRef, useState } from 'react';\nimport { useNavigate, useParams } from 'react-router-dom';\nimport {\n AlertCircle,\n ArrowLeft,\n CheckCircle2,\n Download,\n Loader2,\n Mail,\n RefreshCw,\n Send,\n Upload,\n UserPlus,\n XCircle,\n} from 'lucide-react';\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Badge,\n Button,\n Card,\n CardContent,\n CardHeader,\n CardTitle,\n Checkbox,\n Input,\n Skeleton,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Tooltip,\n TooltipContent,\n TooltipProvider,\n TooltipTrigger,\n} from '@burdenoff/fe-libs/ui';\nimport { PermissionProvider } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { getProjectDisplayNames, getRoleDisplayNames } from '../utils/invitationDisplay';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useWorkspacePermissions } from '../hooks/useWorkspacePermissions';\nimport { useWorkspaceRoles } from '../hooks/useWorkspaceRoles';\nimport { useProjects } from '../hooks/useProjects';\nimport { useWorkspaceInvitations } from '../hooks/useWorkspaceMembers';\nimport {\n useBulkCreateInvitations,\n useCancelInvitation,\n useCreateInvitation,\n useResendInvitation,\n} from '../hooks/useMemberMutations';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\nimport type { Project, WorkspaceInvitation } from '../types';\nimport {\n downloadWorkspaceBulkInviteTemplate,\n parseWorkspaceBulkInviteCsv,\n type WorkspaceBulkInviteRow,\n} from '../utils/workspaceBulkInviteCsv';\n\ntype InviteMode = 'single' | 'bulk';\n\ntype RowStatus =\n | { kind: 'idle' }\n | { kind: 'sending' }\n | { kind: 'invited' }\n | { kind: 'failed'; message: string };\n\ninterface PreviewRow extends WorkspaceBulkInviteRow {\n status: RowStatus;\n}\n\ntype FeedbackState = {\n tone: 'success' | 'error';\n message: string;\n} | null;\n\nconst MAX_BULK_INVITE_FILE_SIZE = 1024 * 1024;\nconst MAX_BULK_INVITE_ROWS = 500;\n\nfunction isCsvFile(file: File): boolean {\n return file.type === 'text/csv' || file.name.toLowerCase().endsWith('.csv');\n}\n\nfunction formatDate(value?: string) {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction formatInviteError(error: unknown): string {\n const message = error instanceof Error ? error.message : String(error);\n if (message.includes('CONFLICT')) {\n return 'An invite already exists for this email address.';\n }\n if (message.includes('already a member')) {\n return 'This user is already a member of the workspace.';\n }\n return 'Something went wrong. Please try again or contact support.';\n}\n\nfunction formatSelectionSummary(\n items: string[],\n emptyLabel: string,\n formatter?: (value: string) => string\n) {\n if (items.length === 0) {\n return emptyLabel;\n }\n\n return items.map((item) => (formatter ? formatter(item) : item)).join(', ');\n}\n\nfunction buildInvitationPayload(\n workspaceId: string,\n email: string,\n roleIds: string[],\n projectIds: string[]\n) {\n return {\n email,\n workspaceId,\n roleIds,\n ...(projectIds.length > 0\n ? {\n context: {\n type: 'workspace.invitation',\n metadata: { projectIds },\n },\n }\n : {}),\n };\n}\n\nfunction WorkspaceSelectorSection({\n selectedRoleIds,\n onRoleToggle,\n selectedProjectIds,\n onProjectToggle,\n availableRoles,\n availableProjects,\n isLoadingRoles,\n isLoadingProjects,\n disabled,\n}: {\n selectedRoleIds: string[];\n onRoleToggle: (roleId: string, checked: boolean) => void;\n selectedProjectIds: string[];\n onProjectToggle: (projectId: string, checked: boolean) => void;\n availableRoles: Array<{\n id: string;\n name: string;\n description?: string | null;\n isSystem: boolean;\n }>;\n availableProjects: Project[];\n isLoadingRoles: boolean;\n isLoadingProjects: boolean;\n disabled: boolean;\n}) {\n return (\n <div className=\"space-y-6\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">Roles (Optional)</h3>\n <p className=\"text-sm text-muted-foreground\">\n Choose roles. Hover any role to preview its full description.\n </p>\n </div>\n {selectedRoleIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedRoleIds.length} selected</Badge>\n ) : null}\n </div>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-muted-foreground\">Loading roles…</p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No assignable roles were returned for this workspace.\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = selectedRoleIds.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-border bg-background text-foreground hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onRoleToggle(role.id, Boolean(checked))}\n aria-label={`Select ${role.name}`}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-muted-foreground\">\n {role.description || 'No description available for this role.'}\n </p>\n {role.isSystem ? <p className=\"mt-2 text-xs\">System role</p> : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">Projects to add on acceptance (Optional)</h3>\n <p className=\"text-sm text-muted-foreground\">\n The invited user will be added to these projects automatically after they accept.\n </p>\n </div>\n {selectedProjectIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedProjectIds.length} selected</Badge>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-muted-foreground\">Loading projects…</p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n No active projects are available in this workspace yet.\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = selectedProjectIds.includes(project.id);\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-ring focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-primary/10 text-foreground shadow-sm'\n : 'border-border bg-background text-foreground hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">{project.name}</p>\n <div className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onProjectToggle(project.id, Boolean(checked))}\n aria-label={`Select ${project.name}`}\n />\n </label>\n );\n })\n )}\n </div>\n </div>\n </div>\n );\n}\n\nfunction WorkspaceInvitePageContent() {\n const { workspaceId: routeWorkspaceId } = useParams<{ workspaceId: string }>();\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/workspaces', workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const workspaceId = routeWorkspaceId || contextWorkspaceId || '';\n\n useWorkspacesPageView('workspaces.invite', { workspaceId: workspaceId || undefined });\n\n const { canInviteMember, isLoading: permissionsLoading } = useWorkspacePermissions();\n const {\n data: workspace,\n isLoading: isLoadingWorkspace,\n error: workspaceError,\n } = useWorkspace(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const { data: invitationsData, isLoading: isLoadingInvitations } = useWorkspaceInvitations(\n workspaceId,\n 'PENDING',\n { limit: 50 }\n );\n const availableProjects = useMemo(\n () => (projectList?.items ?? []).filter((project) => !project.archived),\n [projectList?.items]\n );\n\n const [mode, setMode] = useState<InviteMode>('single');\n const [email, setEmail] = useState('');\n const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);\n const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);\n const [singleError, setSingleError] = useState<string | null>(null);\n const [singleSuccess, setSingleSuccess] = useState<string | null>(null);\n const [bulkError, setBulkError] = useState<string | null>(null);\n const [bulkSuccess, setBulkSuccess] = useState<string | null>(null);\n const [pendingFeedback, setPendingFeedback] = useState<FeedbackState>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [dragActive, setDragActive] = useState(false);\n const [isSubmittingBulk, setIsSubmittingBulk] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [cancelTarget, setCancelTarget] = useState<WorkspaceInvitation | null>(null);\n\n const { mutateAsync: createInvitation, isPending: isCreatingInvitation } =\n useCreateInvitation(workspaceId);\n const { mutateAsync: bulkCreateInvitations, isPending: isCreatingBulkInvites } =\n useBulkCreateInvitations();\n const { mutate: resendInvitation, isPending: isResendingInvitation } =\n useResendInvitation(workspaceId);\n const { mutate: cancelInvitation, isPending: isCancellingInvitation } =\n useCancelInvitation(workspaceId);\n const [invitationActionId, setInvitationActionId] = useState<string | null>(null);\n\n const pendingInvitations = invitationsData?.items ?? [];\n const isMutatingInvite = isCreatingInvitation || isCreatingBulkInvites || isSubmittingBulk;\n const selectedRoleNames = useMemo(\n () =>\n availableRoles.filter((role) => selectedRoleIds.includes(role.id)).map((role) => role.name),\n [availableRoles, selectedRoleIds]\n );\n const selectedProjectNames = useMemo(\n () =>\n availableProjects\n .filter((project) => selectedProjectIds.includes(project.id))\n .map((project) => project.name),\n [availableProjects, selectedProjectIds]\n );\n\n const readyRows = useMemo(\n () => rows.filter((row) => row.errors.length === 0 && row.status.kind !== 'invited'),\n [rows]\n );\n const invalidRows = useMemo(() => rows.filter((row) => row.errors.length > 0), [rows]);\n\n if (!workspaceId) {\n return (\n <div className=\"space-y-4 p-6\">\n <div className=\"rounded-xl border bg-card p-8 text-center\">\n <h1 className=\"text-2xl font-bold\">\n {tWithFallback(t, 'workspaceInvite.noWorkspaceTitle', 'Select a workspace first')}\n </h1>\n <p className=\"mt-2 text-muted-foreground\">\n {tWithFallback(\n t,\n 'workspaceInvite.noWorkspaceDescription',\n 'Choose a current workspace from the header or open a workspace detail page before inviting members.'\n )}\n </p>\n <div className=\"mt-4\">\n <Button\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n variant=\"outline\"\n >\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n if (permissionsLoading || isLoadingWorkspace) {\n return (\n <div className=\"space-y-6 p-6\">\n <Skeleton className=\"h-10 w-48\" />\n <Skeleton className=\"h-40 w-full\" />\n <Skeleton className=\"h-80 w-full\" />\n </div>\n );\n }\n\n if (!canInviteMember) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'workspaceInvite.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'workspaceInvite.accessDeniedDescription',\n \"You don't have permission to invite members into this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (workspaceError || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <div className=\"rounded-xl border bg-card p-8 text-center text-destructive\">\n {tWithFallback(\n t,\n 'workspaceInvite.workspaceLoadError',\n 'Unable to load workspace invite details.'\n )}\n </div>\n </div>\n );\n }\n\n const handleRoleToggle = (roleId: string, checked: boolean) => {\n setSelectedRoleIds((prev) =>\n checked\n ? [...prev, roleId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== roleId)\n );\n };\n\n const handleProjectToggle = (projectId: string, checked: boolean) => {\n setSelectedProjectIds((prev) =>\n checked\n ? [...prev, projectId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== projectId)\n );\n };\n\n const handleSingleInvite = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSingleError(null);\n setSingleSuccess(null);\n setPendingFeedback(null);\n\n if (!email.trim()) {\n setSingleError('Email is required.');\n return;\n }\n\n try {\n await createInvitation(\n buildInvitationPayload(\n workspaceId,\n email.trim().toLowerCase(),\n selectedRoleIds,\n selectedProjectIds\n )\n );\n setEmail('');\n setSingleSuccess(`Invitation sent to ${email.trim().toLowerCase()}.`);\n } catch (error) {\n setSingleError(formatInviteError(error));\n }\n };\n\n const handleFile = (file: File) => {\n if (!isCsvFile(file)) {\n setBulkError('Please upload a .csv file.');\n return;\n }\n\n if (file.size > MAX_BULK_INVITE_FILE_SIZE) {\n setBulkError('CSV file is too large (max 1 MB).');\n return;\n }\n\n const reader = new FileReader();\n reader.onload = () => {\n const text = typeof reader.result === 'string' ? reader.result : '';\n const parsed = parseWorkspaceBulkInviteCsv(text);\n const limitedRows = parsed.slice(0, MAX_BULK_INVITE_ROWS);\n setRows(limitedRows.map((row) => ({ ...row, status: { kind: 'idle' } })));\n setFileName(file.name);\n setBulkError(\n parsed.length > MAX_BULK_INVITE_ROWS\n ? `Only the first ${MAX_BULK_INVITE_ROWS} rows were loaded. Split larger CSV files into smaller batches.`\n : null\n );\n setBulkSuccess(null);\n setPendingFeedback(null);\n };\n reader.onerror = () => {\n setBulkError('Failed to read CSV file.');\n };\n reader.readAsText(file);\n };\n\n const handleBulkInvite = async () => {\n if (readyRows.length === 0 || isSubmittingBulk) {\n return;\n }\n\n setBulkError(null);\n setBulkSuccess(null);\n setPendingFeedback(null);\n setIsSubmittingBulk(true);\n setRows((prev) =>\n prev.map((row) =>\n row.errors.length === 0 && row.status.kind !== 'invited'\n ? { ...row, status: { kind: 'sending' } }\n : row\n )\n );\n\n try {\n await bulkCreateInvitations(\n readyRows.map((row) =>\n buildInvitationPayload(workspaceId, row.email, selectedRoleIds, selectedProjectIds)\n )\n );\n\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'invited' } } : row\n )\n );\n setBulkSuccess(\n `Sent ${readyRows.length} workspace invitation${readyRows.length === 1 ? '' : 's'}.`\n );\n } catch (error) {\n const message = formatInviteError(error);\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'failed', message } } : row\n )\n );\n setBulkError(message);\n } finally {\n setIsSubmittingBulk(false);\n }\n };\n\n const handleResendInvitation = (invitation: WorkspaceInvitation) => {\n setPendingFeedback(null);\n setInvitationActionId(invitation.id);\n resendInvitation(invitation.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation resent to ${invitation.email}.`,\n });\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n const handleConfirmCancel = () => {\n if (!cancelTarget) {\n return;\n }\n\n setPendingFeedback(null);\n setInvitationActionId(cancelTarget.id);\n cancelInvitation(cancelTarget.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation cancelled for ${cancelTarget.email}.`,\n });\n setCancelTarget(null);\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n return (\n <>\n <AlertDialog\n open={Boolean(cancelTarget)}\n onOpenChange={(open) => !open && setCancelTarget(null)}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Cancel pending invitation?</AlertDialogTitle>\n <AlertDialogDescription>\n {cancelTarget\n ? `This will revoke the pending invite for ${cancelTarget.email}. They will need a new invitation to join later.`\n : 'This will revoke the pending invitation.'}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isCancellingInvitation}>Keep invitation</AlertDialogCancel>\n <AlertDialogAction onClick={handleConfirmCancel} disabled={isCancellingInvitation}>\n {isCancellingInvitation ? <Loader2 className=\"size-4 animate-spin\" /> : null}\n Cancel invitation\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n <div className=\"space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}`))}\n className=\"-ml-2 mb-2 gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspace', 'Back to Workspace')}\n </Button>\n <div className=\"flex items-center gap-2\">\n <UserPlus className=\"text-primary\" size={24} />\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'workspaceInvite.title', 'Invite Members')}\n </h1>\n </div>\n <p className=\"mt-2 text-sm text-muted-foreground sm:text-base\">\n {workspace.name} —{' '}\n {tWithFallback(\n t,\n 'workspaceInvite.subtitle',\n 'Invite people into this workspace and optionally grant project access.'\n )}\n </p>\n </div>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceInvite.setupTitle', 'Access to grant on acceptance')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <WorkspaceSelectorSection\n selectedRoleIds={selectedRoleIds}\n onRoleToggle={handleRoleToggle}\n selectedProjectIds={selectedProjectIds}\n onProjectToggle={handleProjectToggle}\n availableRoles={availableRoles}\n availableProjects={availableProjects}\n isLoadingRoles={isLoadingRoles}\n isLoadingProjects={isLoadingProjects}\n disabled={isMutatingInvite}\n />\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>Selection summary</CardTitle>\n </CardHeader>\n <CardContent className=\"grid gap-4 md:grid-cols-2\">\n <div className=\"space-y-2 rounded-lg border p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">Roles</p>\n <Badge variant=\"secondary\">{selectedRoleNames.length}</Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {formatSelectionSummary(selectedRoleNames, 'No roles selected')}\n </p>\n </div>\n <div className=\"space-y-2 rounded-lg border p-4\">\n <div className=\"flex items-center justify-between gap-2\">\n <p className=\"text-sm font-medium\">Projects</p>\n <Badge variant=\"secondary\">{selectedProjectNames.length}</Badge>\n </div>\n <p className=\"text-sm text-muted-foreground\">\n {formatSelectionSummary(\n selectedProjectNames,\n 'No project access will be granted automatically'\n )}\n </p>\n </div>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceInvite.sendTitle', 'Send invitations')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Tabs\n value={mode}\n onValueChange={(value) => setMode(value as InviteMode)}\n className=\"space-y-5\"\n >\n <TabsList>\n <TabsTrigger value=\"single\">\n {tWithFallback(t, 'workspaceInvite.modeSingle', 'Single')}\n </TabsTrigger>\n <TabsTrigger value=\"bulk\">\n {tWithFallback(t, 'workspaceInvite.modeBulk', 'Bulk CSV')}\n </TabsTrigger>\n </TabsList>\n\n <TabsContent value=\"single\" className=\"space-y-4\">\n {singleError ? (\n <div className=\"flex items-center gap-2 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{singleError}</span>\n </div>\n ) : null}\n {singleSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{singleSuccess}</span>\n </div>\n ) : null}\n\n <form onSubmit={handleSingleInvite} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-invite-email\" className=\"text-sm font-medium\">\n {tWithFallback(t, 'workspaceInvite.emailLabel', 'Email Address')}\n </label>\n <Input\n id=\"workspace-invite-email\"\n type=\"email\"\n placeholder=\"member@example.com\"\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n disabled={isCreatingInvitation}\n />\n </div>\n\n <div className=\"flex justify-end\">\n <Button type=\"submit\" disabled={isCreatingInvitation} className=\"gap-2\">\n {isCreatingInvitation ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Send className=\"size-4\" />\n )}\n {tWithFallback(t, 'workspaceInvite.sendSingleButton', 'Send Invitation')}\n </Button>\n </div>\n </form>\n </TabsContent>\n\n <TabsContent value=\"bulk\" className=\"space-y-5\">\n {bulkError ? (\n <div className=\"flex items-center gap-2 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{bulkError}</span>\n </div>\n ) : null}\n {bulkSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{bulkSuccess}</span>\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-1\">\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'workspaceInvite.bulkDescription',\n 'Upload a CSV with one email column. All valid rows will receive the currently selected roles and projects.'\n )}\n </p>\n <div className=\"rounded-md border bg-muted/30 px-3 py-2 text-xs text-muted-foreground\">\n <p className=\"font-medium text-foreground\">Sample format</p>\n <pre className=\"mt-1 whitespace-pre-wrap\">\n email{`\\n`}alice@example.com{`\\n`}bob@example.com\n </pre>\n </div>\n {(projectList?.total ?? 0) > availableProjects.length ? (\n <p className=\"text-xs text-muted-foreground\">\n Showing the first {availableProjects.length} active projects. Narrow the\n workspace project list if you need to invite into more.\n </p>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={downloadWorkspaceBulkInviteTemplate}\n className=\"gap-2\"\n >\n <Download size={16} />\n {tWithFallback(t, 'workspaceInvite.downloadTemplate', 'Download template')}\n </Button>\n </div>\n\n <div\n onDragOver={(event) => {\n event.preventDefault();\n setDragActive(true);\n }}\n onDragLeave={() => setDragActive(false)}\n onDrop={(event) => {\n event.preventDefault();\n setDragActive(false);\n const file = event.dataTransfer.files?.[0];\n if (file) {\n handleFile(file);\n } else {\n setBulkError('Please upload a .csv file.');\n }\n }}\n className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 transition-colors ${\n dragActive ? 'border-primary bg-primary/5' : 'border-border bg-background'\n }`}\n >\n <Upload size={28} className=\"text-muted-foreground\" />\n <div className=\"text-center\">\n <p className=\"text-sm font-medium\">\n {fileName || 'Drop a CSV file here or click to browse'}\n </p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n CSV files only. Required column: email.\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) => {\n const file = event.target.files?.[0];\n if (file) {\n handleFile(file);\n }\n event.target.value = '';\n }}\n className=\"hidden\"\n aria-label=\"Choose CSV file\"\n />\n <Button type=\"button\" onClick={() => fileInputRef.current?.click()}>\n {tWithFallback(t, 'workspaceInvite.chooseFile', 'Choose file')}\n </Button>\n </div>\n\n {rows.length > 0 ? (\n <>\n <div className=\"flex flex-wrap gap-4 text-xs text-muted-foreground\">\n <span>Total rows: {rows.length}</span>\n <span>Ready: {readyRows.length}</span>\n <span>With errors: {invalidRows.length}</span>\n {invalidRows.length > 0 ? (\n <Badge variant=\"destructive\">Invalid rows will be skipped</Badge>\n ) : null}\n </div>\n {invalidRows.length > 0 ? (\n <div className=\"rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive\">\n {invalidRows.length} row{invalidRows.length === 1 ? '' : 's'} need\n attention. Only valid rows can be invited.\n </div>\n ) : null}\n <div className=\"overflow-hidden rounded-xl border\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-muted/40\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n #\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n Email\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n Status\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y\">\n {rows.map((row) => {\n const hasParseErrors = row.errors.length > 0;\n return (\n <tr\n key={`${row.rowNumber}-${row.email}`}\n className={hasParseErrors ? 'bg-destructive/5' : undefined}\n >\n <td className=\"px-3 py-2 text-xs text-muted-foreground\">\n {row.rowNumber}\n </td>\n <td className=\"px-3 py-2 text-sm\">\n {row.email || (\n <span className=\"italic text-muted-foreground\">(empty)</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs\">\n {hasParseErrors ? (\n <div className=\"space-y-1 text-destructive\">\n {row.errors.map((errorText) => (\n <span\n key={`${row.rowNumber}-${errorText}`}\n className=\"inline-flex items-center gap-1 pr-3\"\n >\n <XCircle size={14} />\n {errorText}\n </span>\n ))}\n </div>\n ) : row.status.kind === 'sending' ? (\n <span className=\"inline-flex items-center gap-1 text-muted-foreground\">\n <Loader2 size={14} className=\"animate-spin\" />\n Sending...\n </span>\n ) : row.status.kind === 'invited' ? (\n <span className=\"inline-flex items-center gap-1 text-green-700 dark:text-green-400\">\n <CheckCircle2 size={14} />\n Invited\n </span>\n ) : row.status.kind === 'failed' ? (\n <span className=\"inline-flex items-center gap-1 text-destructive\">\n <XCircle size={14} />\n {row.status.message}\n </span>\n ) : (\n <span className=\"text-muted-foreground\">Ready</span>\n )}\n </td>\n </tr>\n );\n })}\n </tbody>\n </table>\n </div>\n </div>\n </>\n ) : (\n <div className=\"rounded-xl border border-dashed p-6 text-center text-sm text-muted-foreground\">\n Upload a CSV to preview invite rows here before sending.\n </div>\n )}\n\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n onClick={handleBulkInvite}\n disabled={readyRows.length === 0 || isCreatingBulkInvites || isSubmittingBulk}\n className=\"gap-2\"\n >\n {isCreatingBulkInvites || isSubmittingBulk ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Send className=\"size-4\" />\n )}\n {tWithFallback(t, 'workspaceInvite.sendBulkButton', 'Send Bulk Invites')}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n </CardContent>\n </Card>\n\n <Card>\n <CardHeader>\n <CardTitle className=\"flex items-center gap-2\">\n <Mail className=\"size-5 text-primary\" />\n {tWithFallback(t, 'workspaceInvite.pendingTitle', 'Pending Invitations')}\n <Badge variant=\"secondary\">{pendingInvitations.length}</Badge>\n </CardTitle>\n </CardHeader>\n <CardContent>\n {pendingFeedback ? (\n <div\n className={`mb-4 flex items-center gap-2 rounded-md border p-3 text-sm ${\n pendingFeedback.tone === 'success'\n ? 'border-green-500/20 bg-green-500/10 text-green-700 dark:text-green-400'\n : 'border-destructive/20 bg-destructive/10 text-destructive'\n }`}\n >\n {pendingFeedback.tone === 'success' ? (\n <CheckCircle2 className=\"size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"size-4 shrink-0\" />\n )}\n <span>{pendingFeedback.message}</span>\n </div>\n ) : null}\n {isLoadingInvitations ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n </div>\n ) : pendingInvitations.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'workspaceInvite.pendingEmpty',\n 'No pending invitations for this workspace.'\n )}\n </p>\n ) : (\n <div className=\"space-y-3\">\n {pendingInvitations.map((invitation) => {\n const roleNames = getRoleDisplayNames(invitation);\n const projectNames = getProjectDisplayNames(invitation);\n const isRunningAction =\n invitationActionId === invitation.id &&\n (isResendingInvitation || isCancellingInvitation);\n\n return (\n <div key={invitation.id} className=\"rounded-lg border p-4\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-semibold\">{invitation.email}</p>\n <Badge variant=\"outline\">{invitation.status}</Badge>\n </div>\n <div className=\"grid gap-3 text-sm text-muted-foreground sm:grid-cols-2\">\n <div>\n <p className=\"font-medium text-foreground\">Received</p>\n <p>{formatDate(invitation.createdAt) ?? '—'}</p>\n </div>\n <div>\n <p className=\"font-medium text-foreground\">Expires</p>\n <p>{formatDate(invitation.expiresAt) ?? '—'}</p>\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-foreground\">Roles</p>\n <div className=\"flex flex-wrap gap-2\">\n {roleNames.length > 0 ? (\n roleNames.map((roleName, index) => (\n <Badge\n key={`${invitation.id}-${invitation.roleIds[index] ?? roleName}`}\n variant=\"outline\"\n >\n {roleName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-muted-foreground\">\n No roles selected\n </span>\n )}\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-foreground\">Projects</p>\n <div className=\"flex flex-wrap gap-2\">\n {projectNames.length > 0 ? (\n projectNames.map((projectName, index) => (\n <Badge\n key={`${invitation.id}-project-${projectName}-${index}`}\n variant=\"secondary\"\n >\n {projectName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-muted-foreground\">\n No project access will be granted automatically\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => handleResendInvitation(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isResendingInvitation ? (\n <RefreshCw className=\"size-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"size-4\" />\n )}\n Resend\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => setCancelTarget(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isCancellingInvitation ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <XCircle className=\"size-4\" />\n )}\n Cancel\n </Button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </CardContent>\n </Card>\n </div>\n </>\n );\n}\n\nexport function WorkspaceInvitePage() {\n const { workspaceId: routeWorkspaceId } = useParams<{ workspaceId: string }>();\n const { workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const permissionScopeId = routeWorkspaceId || contextWorkspaceId || null;\n\n return (\n <PermissionProvider scopeId={permissionScopeId} gateway=\"workspace\" debug={false}>\n <WorkspaceInvitePageContent />\n </PermissionProvider>\n );\n}\n"],"mappings":"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"}
@@ -8,109 +8,124 @@ import { WORKSPACE_PERMISSIONS as o } from "../constants/permissions.js";
8
8
  import { tWithFallback as s } from "../utils/i18n.js";
9
9
  import { useWorkspacesPageView as c } from "../hooks/useWorkspacesPageView.js";
10
10
  import { useEffect as l, useState as u } from "react";
11
- import { Grid3X3 as d, List as f, Mail as p, Plus as m, Search as h } from "lucide-react";
12
- import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
13
- import { Badge as y, Button as b, Input as x, Skeleton as S } from "@burdenoff/fe-libs/ui";
14
- import { useNavigate as C } from "react-router-dom";
15
- import { useDebounce as w } from "@burdenoff/fe-libs/shared/hooks";
16
- import { PermissionButton as T } from "@burdenoff/fe-libs/shared/components";
17
- import { useI18n as E } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
11
+ import { Grid3X3 as d, List as f, Mail as p, Plus as m, Search as h, UserPlus as g } from "lucide-react";
12
+ import { Fragment as _, jsx as v, jsxs as y } from "react/jsx-runtime";
13
+ import { Badge as b, Button as x, Input as S, Skeleton as C } from "@burdenoff/fe-libs/ui";
14
+ import { useNavigate as w } from "react-router-dom";
15
+ import { useDebounce as T } from "@burdenoff/fe-libs/shared/hooks";
16
+ import { PermissionButton as E } from "@burdenoff/fe-libs/shared/components";
17
+ import { useI18n as D } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
18
18
  //#region src/pages/WorkspacesListPage.tsx
19
- function D() {
19
+ function O() {
20
20
  c("workspaces.list");
21
- let { t: D } = E(), O = C(), { basePath: k = "/", currentUser: A } = e(), { withCurrentSearch: j } = i(), { viewMode: M, setViewMode: N, workspaceFilters: P, setWorkspaceFilters: F } = t(), I = () => {
22
- O(j(`${k}/new`));
23
- }, [L, R] = u(P.search || ""), z = w(L, 500);
21
+ let { t: O } = D(), k = w(), { basePath: A = "/", currentUser: j, workspaceId: M } = e(), { withCurrentSearch: N } = i(), { viewMode: P, setViewMode: F, workspaceFilters: I, setWorkspaceFilters: L } = t(), R = () => {
22
+ k(N(`${A}/new`));
23
+ }, [z, B] = u(I.search || ""), V = T(z, 500);
24
24
  l(() => {
25
- z !== P.search && F({ search: z });
25
+ V !== I.search && L({ search: V });
26
26
  }, [
27
- z,
28
- P.search,
29
- F
27
+ V,
28
+ I.search,
29
+ L
30
30
  ]);
31
- let { data: B, isLoading: V, error: H } = n(), { data: U = [] } = r(A?.email), W = U.length, G = () => {
32
- O(j(`${k}/invites`));
33
- }, K = B ? {
34
- ...B,
35
- items: z ? B.items.filter((e) => e.name.toLowerCase().includes(z.toLowerCase())) : B.items,
36
- total: z ? B.items.filter((e) => e.name.toLowerCase().includes(z.toLowerCase())).length : B.total
31
+ let { data: H, isLoading: U, error: W } = n(), { data: G = [] } = r(j?.email), K = G.length, q = () => {
32
+ k(N(`${A}/invites`));
33
+ }, J = () => {
34
+ M && k(N(`${A}/${M}/invite`));
35
+ }, Y = H ? {
36
+ ...H,
37
+ items: V ? H.items.filter((e) => e.name.toLowerCase().includes(V.toLowerCase())) : H.items,
38
+ total: V ? H.items.filter((e) => e.name.toLowerCase().includes(V.toLowerCase())).length : H.total
37
39
  } : void 0;
38
- return H ? /* @__PURE__ */ _("div", {
40
+ return W ? /* @__PURE__ */ v("div", {
39
41
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
40
- children: /* @__PURE__ */ v("div", {
42
+ children: /* @__PURE__ */ y("div", {
41
43
  className: "flex flex-col items-center justify-center py-12",
42
- children: [/* @__PURE__ */ _("p", {
44
+ children: [/* @__PURE__ */ v("p", {
43
45
  className: "text-destructive text-lg font-semibold",
44
- children: s(D, "workspacesList.loadError", "Error loading workspaces")
45
- }), /* @__PURE__ */ _("p", {
46
+ children: s(O, "workspacesList.loadError", "Error loading workspaces")
47
+ }), /* @__PURE__ */ v("p", {
46
48
  className: "text-muted-foreground text-sm mt-2",
47
- children: H instanceof Error ? H.message : s(D, "workspacesList.unknownError", "Unknown error")
49
+ children: W instanceof Error ? W.message : s(O, "workspacesList.unknownError", "Unknown error")
48
50
  })]
49
51
  })
50
- }) : /* @__PURE__ */ v("div", {
52
+ }) : /* @__PURE__ */ y("div", {
51
53
  className: "space-y-4 sm:space-y-6 p-3 sm:p-6",
52
54
  children: [
53
- /* @__PURE__ */ v("div", {
55
+ /* @__PURE__ */ y("div", {
54
56
  className: "flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3",
55
57
  "data-tour": "workspaces-list-header",
56
- children: [/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("h1", {
58
+ children: [/* @__PURE__ */ y("div", { children: [/* @__PURE__ */ v("h1", {
57
59
  className: "text-2xl sm:text-3xl font-bold",
58
- children: s(D, "workspacesList.title", "Workspaces")
59
- }), /* @__PURE__ */ _("p", {
60
+ children: s(O, "workspacesList.title", "Workspaces")
61
+ }), /* @__PURE__ */ v("p", {
60
62
  className: "text-sm sm:text-base text-muted-foreground",
61
- children: s(D, "workspacesList.description", "Manage and organize your workspaces")
62
- })] }), /* @__PURE__ */ v("div", {
63
+ children: s(O, "workspacesList.description", "Manage and organize your workspaces")
64
+ })] }), /* @__PURE__ */ y("div", {
63
65
  className: "flex items-center gap-2",
64
66
  "data-tour": "workspaces-list-actions",
65
67
  children: [
66
- /* @__PURE__ */ _(b, {
67
- variant: M === "grid" ? "default" : "outline",
68
+ /* @__PURE__ */ v(x, {
69
+ variant: P === "grid" ? "default" : "outline",
68
70
  size: "icon",
69
- onClick: () => N("grid"),
71
+ onClick: () => F("grid"),
70
72
  className: "hidden sm:flex",
71
- "aria-label": s(D, "workspacesList.gridViewAriaLabel", "Show workspaces in grid view"),
72
- title: s(D, "workspacesList.gridViewTitle", "Grid view"),
73
- children: /* @__PURE__ */ _(d, { size: 20 })
73
+ "aria-label": s(O, "workspacesList.gridViewAriaLabel", "Show workspaces in grid view"),
74
+ title: s(O, "workspacesList.gridViewTitle", "Grid view"),
75
+ children: /* @__PURE__ */ v(d, { size: 20 })
74
76
  }),
75
- /* @__PURE__ */ _(b, {
76
- variant: M === "list" ? "default" : "outline",
77
+ /* @__PURE__ */ v(x, {
78
+ variant: P === "list" ? "default" : "outline",
77
79
  size: "icon",
78
- onClick: () => N("list"),
80
+ onClick: () => F("list"),
79
81
  className: "hidden sm:flex",
80
- "aria-label": s(D, "workspacesList.listViewAriaLabel", "Show workspaces in list view"),
81
- title: s(D, "workspacesList.listViewTitle", "List view"),
82
- children: /* @__PURE__ */ _(f, { size: 20 })
82
+ "aria-label": s(O, "workspacesList.listViewAriaLabel", "Show workspaces in list view"),
83
+ title: s(O, "workspacesList.listViewTitle", "List view"),
84
+ children: /* @__PURE__ */ v(f, { size: 20 })
83
85
  }),
84
- /* @__PURE__ */ v(b, {
86
+ /* @__PURE__ */ v(E, {
87
+ permission: o.MEMBER_CREATE,
88
+ deniedBehavior: "disable",
89
+ deniedTooltip: s(O, "workspacesList.inviteDeniedTooltip", "You don't have permission to invite members into the current workspace"),
90
+ children: /* @__PURE__ */ y(x, {
91
+ variant: "outline",
92
+ onClick: J,
93
+ className: "gap-2",
94
+ disabled: !M,
95
+ title: M ? s(O, "workspacesList.inviteMemberTitle", "Invite members into the current workspace") : s(O, "workspacesList.inviteMemberNoWorkspaceTitle", "Select a current workspace first"),
96
+ children: [/* @__PURE__ */ v(g, { size: 18 }), /* @__PURE__ */ v("span", { children: s(O, "workspacesList.inviteMemberButton", "Invite Member") })]
97
+ })
98
+ }),
99
+ /* @__PURE__ */ y(x, {
85
100
  variant: "outline",
86
- onClick: G,
101
+ onClick: q,
87
102
  className: "gap-2",
88
103
  children: [
89
- /* @__PURE__ */ _(p, { size: 18 }),
90
- /* @__PURE__ */ _("span", { children: s(D, "workspacesList.pendingInvitesButton", "Pending Invites") }),
91
- W > 0 ? /* @__PURE__ */ _(y, {
104
+ /* @__PURE__ */ v(p, { size: 18 }),
105
+ /* @__PURE__ */ v("span", { children: s(O, "workspacesList.pendingInvitesButton", "Pending Invites") }),
106
+ K > 0 ? /* @__PURE__ */ v(b, {
92
107
  variant: "secondary",
93
- children: W
108
+ children: K
94
109
  }) : null
95
110
  ]
96
111
  }),
97
- /* @__PURE__ */ _(T, {
112
+ /* @__PURE__ */ v(E, {
98
113
  permission: o.WORKSPACE_CREATE,
99
114
  deniedBehavior: "disable",
100
- deniedTooltip: s(D, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
101
- children: /* @__PURE__ */ v(b, {
102
- onClick: I,
115
+ deniedTooltip: s(O, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
116
+ children: /* @__PURE__ */ y(x, {
117
+ onClick: R,
103
118
  className: "gap-2",
104
119
  "data-tour": "workspaces-create-button",
105
120
  children: [
106
- /* @__PURE__ */ _(m, { size: 20 }),
107
- /* @__PURE__ */ _("span", {
121
+ /* @__PURE__ */ v(m, { size: 20 }),
122
+ /* @__PURE__ */ v("span", {
108
123
  className: "hidden sm:inline",
109
- children: s(D, "workspacesList.createButton", "Create Workspace")
124
+ children: s(O, "workspacesList.createButton", "Create Workspace")
110
125
  }),
111
- /* @__PURE__ */ _("span", {
126
+ /* @__PURE__ */ v("span", {
112
127
  className: "sm:hidden",
113
- children: s(D, "workspacesList.createButtonShort", "Create")
128
+ children: s(O, "workspacesList.createButtonShort", "Create")
114
129
  })
115
130
  ]
116
131
  })
@@ -118,86 +133,86 @@ function D() {
118
133
  ]
119
134
  })]
120
135
  }),
121
- /* @__PURE__ */ v("div", {
136
+ /* @__PURE__ */ y("div", {
122
137
  className: "relative",
123
138
  "data-tour": "workspaces-search-input",
124
- children: [/* @__PURE__ */ _(h, {
139
+ children: [/* @__PURE__ */ v(h, {
125
140
  className: "absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground",
126
141
  size: 20
127
- }), /* @__PURE__ */ _(x, {
128
- placeholder: s(D, "workspacesList.searchPlaceholder", "Search workspaces..."),
129
- value: L,
142
+ }), /* @__PURE__ */ v(S, {
143
+ placeholder: s(O, "workspacesList.searchPlaceholder", "Search workspaces..."),
144
+ value: z,
130
145
  onChange: (e) => {
131
- R(e.target.value);
146
+ B(e.target.value);
132
147
  },
133
148
  className: "pl-[3.25rem]"
134
149
  })]
135
150
  }),
136
- /* @__PURE__ */ _("div", {
151
+ /* @__PURE__ */ v("div", {
137
152
  className: "space-y-4 sm:space-y-6",
138
153
  "data-tour": "workspaces-view-container",
139
- children: V ? /* @__PURE__ */ _("div", {
154
+ children: U ? /* @__PURE__ */ v("div", {
140
155
  className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",
141
- children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ v("div", {
156
+ children: Array.from({ length: 6 }).map((e, t) => /* @__PURE__ */ y("div", {
142
157
  className: "space-y-3 p-6 border rounded-lg",
143
- children: [/* @__PURE__ */ v("div", {
158
+ children: [/* @__PURE__ */ y("div", {
144
159
  className: "flex items-center gap-3",
145
- children: [/* @__PURE__ */ _(S, { className: "size-12 rounded-lg" }), /* @__PURE__ */ v("div", {
160
+ children: [/* @__PURE__ */ v(C, { className: "size-12 rounded-lg" }), /* @__PURE__ */ y("div", {
146
161
  className: "space-y-2 flex-1",
147
- children: [/* @__PURE__ */ _(S, { className: "h-4 w-3/4" }), /* @__PURE__ */ _(S, { className: "h-3 w-1/2" })]
162
+ children: [/* @__PURE__ */ v(C, { className: "h-4 w-3/4" }), /* @__PURE__ */ v(C, { className: "h-3 w-1/2" })]
148
163
  })]
149
- }), /* @__PURE__ */ _(S, { className: "h-3 w-1/3" })]
164
+ }), /* @__PURE__ */ v(C, { className: "h-3 w-1/3" })]
150
165
  }, t))
151
- }) : K?.items.length === 0 ? /* @__PURE__ */ v("div", {
166
+ }) : Y?.items.length === 0 ? /* @__PURE__ */ y("div", {
152
167
  className: "flex flex-col items-center justify-center py-12 text-center",
153
168
  children: [
154
- /* @__PURE__ */ _("div", {
169
+ /* @__PURE__ */ v("div", {
155
170
  className: "rounded-full bg-muted p-6 mb-4",
156
- children: /* @__PURE__ */ _(d, {
171
+ children: /* @__PURE__ */ v(d, {
157
172
  size: 48,
158
173
  className: "text-muted-foreground"
159
174
  })
160
175
  }),
161
- /* @__PURE__ */ _("h3", {
176
+ /* @__PURE__ */ v("h3", {
162
177
  className: "text-lg font-semibold mb-2",
163
- children: s(D, "workspacesList.emptyTitle", "No workspaces found")
178
+ children: s(O, "workspacesList.emptyTitle", "No workspaces found")
164
179
  }),
165
- /* @__PURE__ */ _("p", {
180
+ /* @__PURE__ */ v("p", {
166
181
  className: "text-muted-foreground mb-4 max-w-sm",
167
- children: P.search ? s(D, "workspacesList.emptySearchHint", "Try adjusting your search criteria") : s(D, "workspacesList.emptyCreateHint", "Get started by creating your first workspace")
182
+ children: I.search ? s(O, "workspacesList.emptySearchHint", "Try adjusting your search criteria") : s(O, "workspacesList.emptyCreateHint", "Get started by creating your first workspace")
168
183
  }),
169
- !P.search && /* @__PURE__ */ _(T, {
184
+ !I.search && /* @__PURE__ */ v(E, {
170
185
  permission: o.WORKSPACE_CREATE,
171
186
  deniedBehavior: "disable",
172
- deniedTooltip: s(D, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
173
- children: /* @__PURE__ */ v(b, {
174
- onClick: I,
187
+ deniedTooltip: s(O, "workspacesList.createDeniedTooltip", "You don't have permission to create workspaces"),
188
+ children: /* @__PURE__ */ y(x, {
189
+ onClick: R,
175
190
  className: "gap-2",
176
- children: [/* @__PURE__ */ _(m, { size: 20 }), s(D, "workspacesList.createFirstButton", "Create Your First Workspace")]
191
+ children: [/* @__PURE__ */ v(m, { size: 20 }), s(O, "workspacesList.createFirstButton", "Create Your First Workspace")]
177
192
  })
178
193
  })
179
194
  ]
180
- }) : /* @__PURE__ */ v(g, { children: [M === "list" ? /* @__PURE__ */ _("div", {
195
+ }) : /* @__PURE__ */ y(_, { children: [P === "list" ? /* @__PURE__ */ v("div", {
181
196
  className: "space-y-3",
182
- children: K?.items.map((e) => /* @__PURE__ */ _(a, { workspace: e }, e.id))
183
- }) : /* @__PURE__ */ _("div", {
197
+ children: Y?.items.map((e) => /* @__PURE__ */ v(a, { workspace: e }, e.id))
198
+ }) : /* @__PURE__ */ v("div", {
184
199
  className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",
185
- children: K?.items.map((e) => /* @__PURE__ */ _(a, { workspace: e }, e.id))
186
- }), K && K.total > 0 && /* @__PURE__ */ v("div", {
200
+ children: Y?.items.map((e) => /* @__PURE__ */ v(a, { workspace: e }, e.id))
201
+ }), Y && Y.total > 0 && /* @__PURE__ */ y("div", {
187
202
  className: "flex items-center justify-between text-sm text-muted-foreground",
188
- children: [/* @__PURE__ */ v("p", { children: [
189
- s(D, "workspacesList.showing", "Showing"),
203
+ children: [/* @__PURE__ */ y("p", { children: [
204
+ s(O, "workspacesList.showing", "Showing"),
190
205
  " ",
191
- K.items.length,
206
+ Y.items.length,
192
207
  " ",
193
- s(D, "workspacesList.of", "of"),
208
+ s(O, "workspacesList.of", "of"),
194
209
  " ",
195
- K.total,
210
+ Y.total,
196
211
  " ",
197
- K.total === 1 ? s(D, "workspacesList.workspace", "workspace") : s(D, "workspacesList.workspaces", "workspaces")
198
- ] }), K.hasMore && /* @__PURE__ */ _("p", {
212
+ Y.total === 1 ? s(O, "workspacesList.workspace", "workspace") : s(O, "workspacesList.workspaces", "workspaces")
213
+ ] }), Y.hasMore && /* @__PURE__ */ v("p", {
199
214
  className: "text-xs",
200
- children: s(D, "workspacesList.moreAvailable", "More workspaces available - pagination coming soon")
215
+ children: s(O, "workspacesList.moreAvailable", "More workspaces available - pagination coming soon")
201
216
  })]
202
217
  })] })
203
218
  })
@@ -205,6 +220,6 @@ function D() {
205
220
  });
206
221
  }
207
222
  //#endregion
208
- export { D as WorkspacesListPage };
223
+ export { O as WorkspacesListPage };
209
224
 
210
225
  //# sourceMappingURL=WorkspacesListPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspacesListPage.js","names":[],"sources":["../../src/pages/WorkspacesListPage.tsx"],"sourcesContent":["import { Plus, Grid3X3, List as ListIcon, Search, Mail } from 'lucide-react';\nimport { Badge, Button, Input, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useMyWorkspaces } from '../hooks/useWorkspaces';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { WorkspaceCard } from '../components/WorkspaceCard';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useState, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspacesListPage() {\n useWorkspacesPageView('workspaces.list');\n const { t } = useI18n();\n\n const navigate = useNavigate();\n const { basePath = '/', currentUser } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { viewMode, setViewMode, workspaceFilters, setWorkspaceFilters } = useWorkspacesStore();\n\n // Navigate to unified workspace creation page\n const handleCreateWorkspace = () => {\n navigate(withCurrentSearch(`${basePath}/new`));\n };\n\n const [searchValue, setSearchValue] = useState(workspaceFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n if (debouncedSearch !== workspaceFilters.search) {\n setWorkspaceFilters({ search: debouncedSearch });\n }\n }, [debouncedSearch, workspaceFilters.search, setWorkspaceFilters]);\n\n const { data: rawData, isLoading, error } = useMyWorkspaces();\n const { data: pendingInvitations = [] } = useMyPendingInvitations(currentUser?.email);\n const pendingInvitesCount = pendingInvitations.length;\n\n const handleOpenPendingInvites = () => {\n navigate(withCurrentSearch(`${basePath}/invites`));\n };\n\n // Client-side search filtering (myWorkspaces doesn't accept filter args)\n const data = rawData\n ? {\n ...rawData,\n items: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n )\n : rawData.items,\n total: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n ).length\n : rawData.total,\n }\n : undefined;\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n };\n\n if (error) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center py-12\">\n <p className=\"text-destructive text-lg font-semibold\">\n {tWithFallback(t, 'workspacesList.loadError', 'Error loading workspaces')}\n </p>\n <p className=\"text-muted-foreground text-sm mt-2\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'workspacesList.unknownError', 'Unknown error')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header with title + actions */}\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\"\n data-tour=\"workspaces-list-header\"\n >\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspacesList.title', 'Workspaces')}\n </h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">\n {tWithFallback(t, 'workspacesList.description', 'Manage and organize your workspaces')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\" data-tour=\"workspaces-list-actions\">\n {/* View mode toggle */}\n <Button\n variant={viewMode === 'grid' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('grid')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.gridViewAriaLabel',\n 'Show workspaces in grid view'\n )}\n title={tWithFallback(t, 'workspacesList.gridViewTitle', 'Grid view')}\n >\n <Grid3X3 size={20} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('list')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.listViewAriaLabel',\n 'Show workspaces in list view'\n )}\n title={tWithFallback(t, 'workspacesList.listViewTitle', 'List view')}\n >\n <ListIcon size={20} />\n </Button>\n {/* Create button - requires workspace:create permission */}\n <Button variant=\"outline\" onClick={handleOpenPendingInvites} className=\"gap-2\">\n <Mail size={18} />\n <span>\n {tWithFallback(t, 'workspacesList.pendingInvitesButton', 'Pending Invites')}\n </span>\n {pendingInvitesCount > 0 ? (\n <Badge variant=\"secondary\">{pendingInvitesCount}</Badge>\n ) : null}\n </Button>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button\n onClick={handleCreateWorkspace}\n className=\"gap-2\"\n data-tour=\"workspaces-create-button\"\n >\n <Plus size={20} />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'workspacesList.createButton', 'Create Workspace')}\n </span>\n <span className=\"sm:hidden\">\n {tWithFallback(t, 'workspacesList.createButtonShort', 'Create')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search */}\n <div className=\"relative\" data-tour=\"workspaces-search-input\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <Input\n placeholder={tWithFallback(t, 'workspacesList.searchPlaceholder', 'Search workspaces...')}\n value={searchValue}\n onChange={handleSearchChange}\n className=\"pl-[3.25rem]\"\n />\n </div>\n\n {/* Content */}\n <div className=\"space-y-4 sm:space-y-6\" data-tour=\"workspaces-view-container\">\n {isLoading ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"space-y-3 p-6 border rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <div className=\"space-y-2 flex-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n ))}\n </div>\n ) : data?.items.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 <Grid3X3 size={48} className=\"text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'workspacesList.emptyTitle', 'No workspaces found')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-sm\">\n {workspaceFilters.search\n ? tWithFallback(\n t,\n 'workspacesList.emptySearchHint',\n 'Try adjusting your search criteria'\n )\n : tWithFallback(\n t,\n 'workspacesList.emptyCreateHint',\n 'Get started by creating your first workspace'\n )}\n </p>\n {!workspaceFilters.search && (\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button onClick={handleCreateWorkspace} className=\"gap-2\">\n <Plus size={20} />\n {tWithFallback(\n t,\n 'workspacesList.createFirstButton',\n 'Create Your First Workspace'\n )}\n </Button>\n </PermissionButton>\n )}\n </div>\n ) : (\n <>\n {/* View */}\n {viewMode === 'list' ? (\n <div className=\"space-y-3\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n )}\n\n {/* Pagination info */}\n {data && data.total > 0 && (\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <p>\n {tWithFallback(t, 'workspacesList.showing', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'workspacesList.of', 'of')} {data.total}{' '}\n {data.total !== 1\n ? tWithFallback(t, 'workspacesList.workspaces', 'workspaces')\n : tWithFallback(t, 'workspacesList.workspace', 'workspace')}\n </p>\n {data.hasMore && (\n <p className=\"text-xs\">\n {tWithFallback(\n t,\n 'workspacesList.moreAvailable',\n 'More workspaces available - pagination coming soon'\n )}\n </p>\n )}\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,SAAgB,IAAqB;AACnC,GAAsB,kBAAkB;CACxC,IAAM,EAAE,SAAM,GAAS,EAEjB,IAAW,GAAa,EACxB,EAAE,cAAW,KAAK,mBAAgB,GAAsB,EACxD,EAAE,yBAAsB,GAAqB,EAC7C,EAAE,aAAU,gBAAa,qBAAkB,2BAAwB,GAAoB,EAGvF,UAA8B;AAClC,IAAS,EAAkB,GAAG,EAAS,MAAM,CAAC;IAG1C,CAAC,GAAa,KAAkB,EAAS,EAAiB,UAAU,GAAG,EACvE,IAAkB,EAAY,GAAa,IAAI;AAGrD,SAAgB;AACd,EAAI,MAAoB,EAAiB,UACvC,EAAoB,EAAE,QAAQ,GAAiB,CAAC;IAEjD;EAAC;EAAiB,EAAiB;EAAQ;EAAoB,CAAC;CAEnE,IAAM,EAAE,MAAM,GAAS,cAAW,aAAU,GAAiB,EACvD,EAAE,MAAM,IAAqB,EAAE,KAAK,EAAwB,GAAa,MAAM,EAC/E,IAAsB,EAAmB,QAEzC,UAAiC;AACrC,IAAS,EAAkB,GAAG,EAAS,UAAU,CAAC;IAI9C,IAAO,IACT;EACE,GAAG;EACH,OAAO,IACH,EAAQ,MAAM,QAAQ,MACpB,EAAG,KAAK,aAAa,CAAC,SAAS,EAAgB,aAAa,CAAC,CAC9D,GACD,EAAQ;EACZ,OAAO,IACH,EAAQ,MAAM,QAAQ,MACpB,EAAG,KAAK,aAAa,CAAC,SAAS,EAAgB,aAAa,CAAC,CAC9D,CAAC,SACF,EAAQ;EACb,GACD,KAAA;AAuBJ,QAjBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAG,4BAA4B,2BAA2B;IACvE,CAAA,EACJ,kBAAC,KAAD;IAAG,WAAU;cACV,aAAiB,QACd,EAAM,UACN,EAAc,GAAG,+BAA+B,gBAAgB;IAClE,CAAA,CACA;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IACE,WAAU;IACV,aAAU;cAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAc,GAAG,wBAAwB,aAAa;KACpD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAc,GAAG,8BAA8B,sCAAsC;KACpF,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;KAA0B,aAAU;eAAnD;MAEE,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAU;OACV,cAAY,EACV,GACA,oCACA,+BACD;OACD,OAAO,EAAc,GAAG,gCAAgC,YAAY;iBAEpE,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;OACd,CAAA;MACT,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAU;OACV,cAAY,EACV,GACA,oCACA,+BACD;OACD,OAAO,EAAc,GAAG,gCAAgC,YAAY;iBAEpE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;OACf,CAAA;MAET,kBAAC,GAAD;OAAQ,SAAQ;OAAU,SAAS;OAA0B,WAAU;iBAAvE;QACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;QAClB,kBAAC,QAAD,EAAA,UACG,EAAc,GAAG,uCAAuC,kBAAkB,EACtE,CAAA;QACN,IAAsB,IACrB,kBAAC,GAAD;SAAO,SAAQ;mBAAa;SAA4B,CAAA,GACtD;QACG;;MACT,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,sCACA,iDACD;iBAED,kBAAC,GAAD;QACE,SAAS;QACT,WAAU;QACV,aAAU;kBAHZ;SAKE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;SAClB,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAc,GAAG,+BAA+B,mBAAmB;UAC/D,CAAA;SACP,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAc,GAAG,oCAAoC,SAAS;UAC1D,CAAA;SACA;;OACQ,CAAA;MACf;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAW,aAAU;cAApC,CACE,kBAAC,GAAD;KACE,WAAU;KACV,MAAM;KACN,CAAA,EACF,kBAAC,GAAD;KACE,aAAa,EAAc,GAAG,oCAAoC,uBAAuB;KACzF,OAAO;KACP,WA/GoB,MAA2C;AACrE,QAAe,EAAE,OAAO,MAAM;;KA+GxB,WAAU;KACV,CAAA,CACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAyB,aAAU;cAC/C,IACC,kBAAC,OAAD;KAAK,WAAU;eACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,sBAAuB,CAAA,EAC3C,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,EAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;UACF;UACN,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;QATI,EASJ,CACN;KACE,CAAA,GACJ,GAAM,MAAM,WAAW,IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAS,MAAM;QAAI,WAAU;QAA0B,CAAA;OACnD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,6BAA6B,sBAAsB;OAClE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAiB,SACd,EACE,GACA,kCACA,qCACD,GACD,EACE,GACA,kCACA,+CACD;OACH,CAAA;MACH,CAAC,EAAiB,UACjB,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,sCACA,iDACD;iBAED,kBAAC,GAAD;QAAQ,SAAS;QAAuB,WAAU;kBAAlD,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EACC,GACA,oCACA,8BACD,CACM;;OACQ,CAAA;MAEjB;SAEN,kBAAA,GAAA,EAAA,UAAA,CAEG,MAAa,SACZ,kBAAC,OAAD;KAAK,WAAU;eACZ,GAAM,MAAM,KAAK,MAChB,kBAAC,GAAD,EAA6C,cAAa,EAAtC,EAAU,GAA4B,CAC1D;KACE,CAAA,GAEN,kBAAC,OAAD;KAAK,WAAU;eACZ,GAAM,MAAM,KAAK,MAChB,kBAAC,GAAD,EAA6C,cAAa,EAAtC,EAAU,GAA4B,CAC1D;KACE,CAAA,EAIP,KAAQ,EAAK,QAAQ,KACpB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD,EAAA,UAAA;MACG,EAAc,GAAG,0BAA0B,UAAU;MAAC;MAAE,EAAK,MAAM;MAAQ;MAC3E,EAAc,GAAG,qBAAqB,KAAK;MAAC;MAAE,EAAK;MAAO;MAC1D,EAAK,UAAU,IAEZ,EAAc,GAAG,4BAA4B,YAAY,GADzD,EAAc,GAAG,6BAA6B,aAAa;MAE7D,EAAA,CAAA,EACH,EAAK,WACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,GACA,gCACA,qDACD;MACC,CAAA,CAEF;OAEP,EAAA,CAAA;IAED,CAAA;GACF"}
1
+ {"version":3,"file":"WorkspacesListPage.js","names":[],"sources":["../../src/pages/WorkspacesListPage.tsx"],"sourcesContent":["import { Plus, Grid3X3, List as ListIcon, Search, Mail, UserPlus } from 'lucide-react';\nimport { Badge, Button, Input, Skeleton } from '@burdenoff/fe-libs/ui';\nimport { useNavigate } from 'react-router-dom';\nimport { useWorkspacesStore } from '../store/workspacesStore';\nimport { useMyWorkspaces } from '../hooks/useWorkspaces';\nimport { useMyPendingInvitations } from '../hooks/usePendingInvitations';\nimport { WorkspaceCard } from '../components/WorkspaceCard';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useDebounce } from '@burdenoff/fe-libs/shared/hooks';\nimport { PermissionButton } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useState, useEffect } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { tWithFallback } from '../utils/i18n';\nimport { useScopedNavigation } from '../hooks/useScopedNavigation';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nexport function WorkspacesListPage() {\n useWorkspacesPageView('workspaces.list');\n const { t } = useI18n();\n\n const navigate = useNavigate();\n const { basePath = '/', currentUser, workspaceId: currentWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const { viewMode, setViewMode, workspaceFilters, setWorkspaceFilters } = useWorkspacesStore();\n\n // Navigate to unified workspace creation page\n const handleCreateWorkspace = () => {\n navigate(withCurrentSearch(`${basePath}/new`));\n };\n\n const [searchValue, setSearchValue] = useState(workspaceFilters.search || '');\n const debouncedSearch = useDebounce(searchValue, 500);\n\n // Update filters when debounced search changes\n useEffect(() => {\n if (debouncedSearch !== workspaceFilters.search) {\n setWorkspaceFilters({ search: debouncedSearch });\n }\n }, [debouncedSearch, workspaceFilters.search, setWorkspaceFilters]);\n\n const { data: rawData, isLoading, error } = useMyWorkspaces();\n const { data: pendingInvitations = [] } = useMyPendingInvitations(currentUser?.email);\n const pendingInvitesCount = pendingInvitations.length;\n\n const handleOpenPendingInvites = () => {\n navigate(withCurrentSearch(`${basePath}/invites`));\n };\n\n const handleInviteMember = () => {\n if (!currentWorkspaceId) {\n return;\n }\n\n navigate(withCurrentSearch(`${basePath}/${currentWorkspaceId}/invite`));\n };\n\n // Client-side search filtering (myWorkspaces doesn't accept filter args)\n const data = rawData\n ? {\n ...rawData,\n items: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n )\n : rawData.items,\n total: debouncedSearch\n ? rawData.items.filter((ws) =>\n ws.name.toLowerCase().includes(debouncedSearch.toLowerCase())\n ).length\n : rawData.total,\n }\n : undefined;\n\n const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n setSearchValue(e.target.value);\n };\n\n if (error) {\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col items-center justify-center py-12\">\n <p className=\"text-destructive text-lg font-semibold\">\n {tWithFallback(t, 'workspacesList.loadError', 'Error loading workspaces')}\n </p>\n <p className=\"text-muted-foreground text-sm mt-2\">\n {error instanceof Error\n ? error.message\n : tWithFallback(t, 'workspacesList.unknownError', 'Unknown error')}\n </p>\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n {/* Header with title + actions */}\n <div\n className=\"flex flex-col sm:flex-row sm:items-center sm:justify-between gap-3\"\n data-tour=\"workspaces-list-header\"\n >\n <div>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspacesList.title', 'Workspaces')}\n </h1>\n <p className=\"text-sm sm:text-base text-muted-foreground\">\n {tWithFallback(t, 'workspacesList.description', 'Manage and organize your workspaces')}\n </p>\n </div>\n <div className=\"flex items-center gap-2\" data-tour=\"workspaces-list-actions\">\n {/* View mode toggle */}\n <Button\n variant={viewMode === 'grid' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('grid')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.gridViewAriaLabel',\n 'Show workspaces in grid view'\n )}\n title={tWithFallback(t, 'workspacesList.gridViewTitle', 'Grid view')}\n >\n <Grid3X3 size={20} />\n </Button>\n <Button\n variant={viewMode === 'list' ? 'default' : 'outline'}\n size=\"icon\"\n onClick={() => setViewMode('list')}\n className=\"hidden sm:flex\"\n aria-label={tWithFallback(\n t,\n 'workspacesList.listViewAriaLabel',\n 'Show workspaces in list view'\n )}\n title={tWithFallback(t, 'workspacesList.listViewTitle', 'List view')}\n >\n <ListIcon size={20} />\n </Button>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.MEMBER_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.inviteDeniedTooltip',\n \"You don't have permission to invite members into the current workspace\"\n )}\n >\n <Button\n variant=\"outline\"\n onClick={handleInviteMember}\n className=\"gap-2\"\n disabled={!currentWorkspaceId}\n title={\n currentWorkspaceId\n ? tWithFallback(\n t,\n 'workspacesList.inviteMemberTitle',\n 'Invite members into the current workspace'\n )\n : tWithFallback(\n t,\n 'workspacesList.inviteMemberNoWorkspaceTitle',\n 'Select a current workspace first'\n )\n }\n >\n <UserPlus size={18} />\n <span>{tWithFallback(t, 'workspacesList.inviteMemberButton', 'Invite Member')}</span>\n </Button>\n </PermissionButton>\n <Button variant=\"outline\" onClick={handleOpenPendingInvites} className=\"gap-2\">\n <Mail size={18} />\n <span>\n {tWithFallback(t, 'workspacesList.pendingInvitesButton', 'Pending Invites')}\n </span>\n {pendingInvitesCount > 0 ? (\n <Badge variant=\"secondary\">{pendingInvitesCount}</Badge>\n ) : null}\n </Button>\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button\n onClick={handleCreateWorkspace}\n className=\"gap-2\"\n data-tour=\"workspaces-create-button\"\n >\n <Plus size={20} />\n <span className=\"hidden sm:inline\">\n {tWithFallback(t, 'workspacesList.createButton', 'Create Workspace')}\n </span>\n <span className=\"sm:hidden\">\n {tWithFallback(t, 'workspacesList.createButtonShort', 'Create')}\n </span>\n </Button>\n </PermissionButton>\n </div>\n </div>\n\n {/* Search */}\n <div className=\"relative\" data-tour=\"workspaces-search-input\">\n <Search\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground\"\n size={20}\n />\n <Input\n placeholder={tWithFallback(t, 'workspacesList.searchPlaceholder', 'Search workspaces...')}\n value={searchValue}\n onChange={handleSearchChange}\n className=\"pl-[3.25rem]\"\n />\n </div>\n\n {/* Content */}\n <div className=\"space-y-4 sm:space-y-6\" data-tour=\"workspaces-view-container\">\n {isLoading ? (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"space-y-3 p-6 border rounded-lg\">\n <div className=\"flex items-center gap-3\">\n <Skeleton className=\"size-12 rounded-lg\" />\n <div className=\"space-y-2 flex-1\">\n <Skeleton className=\"h-4 w-3/4\" />\n <Skeleton className=\"h-3 w-1/2\" />\n </div>\n </div>\n <Skeleton className=\"h-3 w-1/3\" />\n </div>\n ))}\n </div>\n ) : data?.items.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 <Grid3X3 size={48} className=\"text-muted-foreground\" />\n </div>\n <h3 className=\"text-lg font-semibold mb-2\">\n {tWithFallback(t, 'workspacesList.emptyTitle', 'No workspaces found')}\n </h3>\n <p className=\"text-muted-foreground mb-4 max-w-sm\">\n {workspaceFilters.search\n ? tWithFallback(\n t,\n 'workspacesList.emptySearchHint',\n 'Try adjusting your search criteria'\n )\n : tWithFallback(\n t,\n 'workspacesList.emptyCreateHint',\n 'Get started by creating your first workspace'\n )}\n </p>\n {!workspaceFilters.search && (\n <PermissionButton\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tWithFallback(\n t,\n 'workspacesList.createDeniedTooltip',\n \"You don't have permission to create workspaces\"\n )}\n >\n <Button onClick={handleCreateWorkspace} className=\"gap-2\">\n <Plus size={20} />\n {tWithFallback(\n t,\n 'workspacesList.createFirstButton',\n 'Create Your First Workspace'\n )}\n </Button>\n </PermissionButton>\n )}\n </div>\n ) : (\n <>\n {/* View */}\n {viewMode === 'list' ? (\n <div className=\"space-y-3\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n ) : (\n <div className=\"grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4\">\n {data?.items.map((workspace) => (\n <WorkspaceCard key={workspace.id} workspace={workspace} />\n ))}\n </div>\n )}\n\n {/* Pagination info */}\n {data && data.total > 0 && (\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <p>\n {tWithFallback(t, 'workspacesList.showing', 'Showing')} {data.items.length}{' '}\n {tWithFallback(t, 'workspacesList.of', 'of')} {data.total}{' '}\n {data.total !== 1\n ? tWithFallback(t, 'workspacesList.workspaces', 'workspaces')\n : tWithFallback(t, 'workspacesList.workspace', 'workspace')}\n </p>\n {data.hasMore && (\n <p className=\"text-xs\">\n {tWithFallback(\n t,\n 'workspacesList.moreAvailable',\n 'More workspaces available - pagination coming soon'\n )}\n </p>\n )}\n </div>\n )}\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiBA,SAAgB,IAAqB;AACnC,GAAsB,kBAAkB;CACxC,IAAM,EAAE,SAAM,GAAS,EAEjB,IAAW,GAAa,EACxB,EAAE,cAAW,KAAK,gBAAa,aAAa,MAAuB,GAAsB,EACzF,EAAE,yBAAsB,GAAqB,EAC7C,EAAE,aAAU,gBAAa,qBAAkB,2BAAwB,GAAoB,EAGvF,UAA8B;AAClC,IAAS,EAAkB,GAAG,EAAS,MAAM,CAAC;IAG1C,CAAC,GAAa,KAAkB,EAAS,EAAiB,UAAU,GAAG,EACvE,IAAkB,EAAY,GAAa,IAAI;AAGrD,SAAgB;AACd,EAAI,MAAoB,EAAiB,UACvC,EAAoB,EAAE,QAAQ,GAAiB,CAAC;IAEjD;EAAC;EAAiB,EAAiB;EAAQ;EAAoB,CAAC;CAEnE,IAAM,EAAE,MAAM,GAAS,cAAW,aAAU,GAAiB,EACvD,EAAE,MAAM,IAAqB,EAAE,KAAK,EAAwB,GAAa,MAAM,EAC/E,IAAsB,EAAmB,QAEzC,UAAiC;AACrC,IAAS,EAAkB,GAAG,EAAS,UAAU,CAAC;IAG9C,UAA2B;AAC1B,OAIL,EAAS,EAAkB,GAAG,EAAS,GAAG,EAAmB,SAAS,CAAC;IAInE,IAAO,IACT;EACE,GAAG;EACH,OAAO,IACH,EAAQ,MAAM,QAAQ,MACpB,EAAG,KAAK,aAAa,CAAC,SAAS,EAAgB,aAAa,CAAC,CAC9D,GACD,EAAQ;EACZ,OAAO,IACH,EAAQ,MAAM,QAAQ,MACpB,EAAG,KAAK,aAAa,CAAC,SAAS,EAAgB,aAAa,CAAC,CAC9D,CAAC,SACF,EAAQ;EACb,GACD,KAAA;AAuBJ,QAjBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,KAAD;IAAG,WAAU;cACV,EAAc,GAAG,4BAA4B,2BAA2B;IACvE,CAAA,EACJ,kBAAC,KAAD;IAAG,WAAU;cACV,aAAiB,QACd,EAAM,UACN,EAAc,GAAG,+BAA+B,gBAAgB;IAClE,CAAA,CACA;;EACF,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GAEE,kBAAC,OAAD;IACE,WAAU;IACV,aAAU;cAFZ,CAIE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAc,GAAG,wBAAwB,aAAa;KACpD,CAAA,EACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAc,GAAG,8BAA8B,sCAAsC;KACpF,CAAA,CACA,EAAA,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;KAA0B,aAAU;eAAnD;MAEE,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAU;OACV,cAAY,EACV,GACA,oCACA,+BACD;OACD,OAAO,EAAc,GAAG,gCAAgC,YAAY;iBAEpE,kBAAC,GAAD,EAAS,MAAM,IAAM,CAAA;OACd,CAAA;MACT,kBAAC,GAAD;OACE,SAAS,MAAa,SAAS,YAAY;OAC3C,MAAK;OACL,eAAe,EAAY,OAAO;OAClC,WAAU;OACV,cAAY,EACV,GACA,oCACA,+BACD;OACD,OAAO,EAAc,GAAG,gCAAgC,YAAY;iBAEpE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA;OACf,CAAA;MACT,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,sCACA,yEACD;iBAED,kBAAC,GAAD;QACE,SAAQ;QACR,SAAS;QACT,WAAU;QACV,UAAU,CAAC;QACX,OACE,IACI,EACE,GACA,oCACA,4CACD,GACD,EACE,GACA,+CACA,mCACD;kBAhBT,CAmBE,kBAAC,GAAD,EAAU,MAAM,IAAM,CAAA,EACtB,kBAAC,QAAD,EAAA,UAAO,EAAc,GAAG,qCAAqC,gBAAgB,EAAQ,CAAA,CAC9E;;OACQ,CAAA;MACnB,kBAAC,GAAD;OAAQ,SAAQ;OAAU,SAAS;OAA0B,WAAU;iBAAvE;QACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;QAClB,kBAAC,QAAD,EAAA,UACG,EAAc,GAAG,uCAAuC,kBAAkB,EACtE,CAAA;QACN,IAAsB,IACrB,kBAAC,GAAD;SAAO,SAAQ;mBAAa;SAA4B,CAAA,GACtD;QACG;;MACT,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,sCACA,iDACD;iBAED,kBAAC,GAAD;QACE,SAAS;QACT,WAAU;QACV,aAAU;kBAHZ;SAKE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA;SAClB,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAc,GAAG,+BAA+B,mBAAmB;UAC/D,CAAA;SACP,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAc,GAAG,oCAAoC,SAAS;UAC1D,CAAA;SACA;;OACQ,CAAA;MACf;OACF;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAW,aAAU;cAApC,CACE,kBAAC,GAAD;KACE,WAAU;KACV,MAAM;KACN,CAAA,EACF,kBAAC,GAAD;KACE,aAAa,EAAc,GAAG,oCAAoC,uBAAuB;KACzF,OAAO;KACP,WA9IoB,MAA2C;AACrE,QAAe,EAAE,OAAO,MAAM;;KA8IxB,WAAU;KACV,CAAA,CACE;;GAGN,kBAAC,OAAD;IAAK,WAAU;IAAyB,aAAU;cAC/C,IACC,kBAAC,OAAD;KAAK,WAAU;eACZ,MAAM,KAAK,EAAE,QAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,MACjC,kBAAC,OAAD;MAAa,WAAU;gBAAvB,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,sBAAuB,CAAA,EAC3C,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,EAClC,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;UACF;UACN,kBAAC,GAAD,EAAU,WAAU,aAAc,CAAA,CAC9B;QATI,EASJ,CACN;KACE,CAAA,GACJ,GAAM,MAAM,WAAW,IACzB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD;QAAS,MAAM;QAAI,WAAU;QAA0B,CAAA;OACnD,CAAA;MACN,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAc,GAAG,6BAA6B,sBAAsB;OAClE,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EAAiB,SACd,EACE,GACA,kCACA,qCACD,GACD,EACE,GACA,kCACA,+CACD;OACH,CAAA;MACH,CAAC,EAAiB,UACjB,kBAAC,GAAD;OACE,YAAY,EAAsB;OAClC,gBAAe;OACf,eAAe,EACb,GACA,sCACA,iDACD;iBAED,kBAAC,GAAD;QAAQ,SAAS;QAAuB,WAAU;kBAAlD,CACE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EACC,GACA,oCACA,8BACD,CACM;;OACQ,CAAA;MAEjB;SAEN,kBAAA,GAAA,EAAA,UAAA,CAEG,MAAa,SACZ,kBAAC,OAAD;KAAK,WAAU;eACZ,GAAM,MAAM,KAAK,MAChB,kBAAC,GAAD,EAA6C,cAAa,EAAtC,EAAU,GAA4B,CAC1D;KACE,CAAA,GAEN,kBAAC,OAAD;KAAK,WAAU;eACZ,GAAM,MAAM,KAAK,MAChB,kBAAC,GAAD,EAA6C,cAAa,EAAtC,EAAU,GAA4B,CAC1D;KACE,CAAA,EAIP,KAAQ,EAAK,QAAQ,KACpB,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD,EAAA,UAAA;MACG,EAAc,GAAG,0BAA0B,UAAU;MAAC;MAAE,EAAK,MAAM;MAAQ;MAC3E,EAAc,GAAG,qBAAqB,KAAK;MAAC;MAAE,EAAK;MAAO;MAC1D,EAAK,UAAU,IAEZ,EAAc,GAAG,4BAA4B,YAAY,GADzD,EAAc,GAAG,6BAA6B,aAAa;MAE7D,EAAA,CAAA,EACH,EAAK,WACJ,kBAAC,KAAD;MAAG,WAAU;gBACV,EACC,GACA,gCACA,qDACD;MACC,CAAA,CAEF;OAEP,EAAA,CAAA;IAED,CAAA;GACF"}
@@ -0,0 +1,25 @@
1
+ //#region src/utils/invitationDisplay.ts
2
+ function e(e) {
3
+ if (e.workspaceName) return e.workspaceName;
4
+ let t = e.context?.metadata?.workspaceName;
5
+ return typeof t == "string" && t.trim().length > 0 ? t : e.workspaceId || "—";
6
+ }
7
+ function t(e) {
8
+ if (Array.isArray(e.roleNames) && e.roleNames.length > 0) return e.roleNames;
9
+ let t = e.context?.metadata?.roleNames;
10
+ return Array.isArray(t) && t.every((e) => typeof e == "string") ? t : e.roleIds;
11
+ }
12
+ function n(e) {
13
+ let t = e.context?.metadata?.projectNames;
14
+ if (Array.isArray(t) && t.every((e) => typeof e == "string")) return t;
15
+ let n = e.context?.metadata?.projectIds, r = [
16
+ ...Array.isArray(e.projectIds) ? e.projectIds : [],
17
+ ...e.projectId ? [e.projectId] : [],
18
+ ...Array.isArray(n) ? n.filter((e) => typeof e == "string") : []
19
+ ], i = new Set(r).size;
20
+ return i === 0 ? [] : [`${i} project${i === 1 ? "" : "s"} selected`];
21
+ }
22
+ //#endregion
23
+ export { n as getProjectDisplayNames, t as getRoleDisplayNames, e as getWorkspaceDisplayName };
24
+
25
+ //# sourceMappingURL=invitationDisplay.js.map