@burdenoff/microfe-workspaces 2026.623.1 → 2026.624.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/CreateWorkspaceDialog.js +5 -5
- package/dist/components/CreateWorkspaceDialog.js.map +1 -1
- package/dist/components/InviteMemberDialog.js +11 -11
- package/dist/components/InviteMemberDialog.js.map +1 -1
- package/dist/components/ProjectCard.js +5 -5
- package/dist/components/ProjectCard.js.map +1 -1
- package/dist/components/ProjectMemberPickerDialog.js +14 -14
- package/dist/components/ProjectMemberPickerDialog.js.map +1 -1
- package/dist/components/WorkspaceCard.js +3 -3
- package/dist/components/WorkspaceCard.js.map +1 -1
- package/dist/components/WorkspacesErrorBoundary.js +9 -9
- package/dist/components/WorkspacesErrorBoundary.js.map +1 -1
- package/dist/components/tags/ProjectTagsWidget.js +8 -8
- package/dist/components/tags/ProjectTagsWidget.js.map +1 -1
- package/dist/pages/ActivityListPage.js +12 -12
- package/dist/pages/ActivityListPage.js.map +1 -1
- package/dist/pages/CreateWorkspacePage.js +24 -24
- package/dist/pages/CreateWorkspacePage.js.map +1 -1
- package/dist/pages/InviteAcceptancePage.js +46 -46
- package/dist/pages/InviteAcceptancePage.js.map +1 -1
- package/dist/pages/MembersListPage.js +27 -27
- package/dist/pages/MembersListPage.js.map +1 -1
- package/dist/pages/PendingInvitesPage.js +23 -23
- package/dist/pages/PendingInvitesPage.js.map +1 -1
- package/dist/pages/ProjectDetailPage.js +20 -20
- package/dist/pages/ProjectDetailPage.js.map +1 -1
- package/dist/pages/ProjectMembersPage.js +15 -15
- package/dist/pages/ProjectMembersPage.js.map +1 -1
- package/dist/pages/ProjectsListPage.js +16 -16
- package/dist/pages/ProjectsListPage.js.map +1 -1
- package/dist/pages/WorkspaceDetailPage.js +16 -16
- package/dist/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/pages/WorkspaceInvitePage.js +51 -51
- package/dist/pages/WorkspaceInvitePage.js.map +1 -1
- package/dist/pages/WorkspaceSettingsPage.js +2 -2
- package/dist/pages/WorkspaceSettingsPage.js.map +1 -1
- package/dist/pages/WorkspacesListPage.js +8 -8
- package/dist/pages/WorkspacesListPage.js.map +1 -1
- package/dist/pages/dashboard/DashboardCustomizer.js +3 -3
- package/dist/pages/dashboard/DashboardCustomizer.js.map +1 -1
- package/dist/pages/dashboard/DashboardGrid.js +2 -2
- package/dist/pages/dashboard/DashboardGrid.js.map +1 -1
- package/dist/pages/dashboard/DashboardPage.js +5 -5
- package/dist/pages/dashboard/DashboardPage.js.map +1 -1
- package/dist/pages/dashboard/WidgetWrapper.js +6 -6
- package/dist/pages/dashboard/WidgetWrapper.js.map +1 -1
- package/dist/pages/dashboard/widgets/AgentsWidget.js +6 -6
- package/dist/pages/dashboard/widgets/AgentsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/GroupsWidget.js +3 -3
- package/dist/pages/dashboard/widgets/GroupsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/MembersWidget.js +3 -3
- package/dist/pages/dashboard/widgets/MembersWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/ProjectsWidget.js +2 -2
- package/dist/pages/dashboard/widgets/ProjectsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js +3 -3
- package/dist/pages/dashboard/widgets/RecentActivityWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/SessionsWidget.js +4 -4
- package/dist/pages/dashboard/widgets/SessionsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/TagCloudWidget.js +2 -2
- package/dist/pages/dashboard/widgets/TagCloudWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/TipsWidget.js +3 -3
- package/dist/pages/dashboard/widgets/TipsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js +2 -2
- package/dist/pages/dashboard/widgets/UpcomingEventsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/VibesWidget.js +2 -2
- package/dist/pages/dashboard/widgets/VibesWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WelcomeWidget.js +3 -3
- package/dist/pages/dashboard/widgets/WelcomeWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js +4 -4
- package/dist/pages/dashboard/widgets/WorkflowStatsWidget.js.map +1 -1
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js +2 -2
- package/dist/pages/dashboard/widgets/WorkspacesWidget.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +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 {\n data: invitationsData,\n isLoading: isLoadingInvitations,\n isFetching: isFetchingInvitations,\n error: invitationsError,\n refetch: refetchInvitations,\n } = useWorkspaceInvitations(workspaceId, 'PENDING', { limit: 50 }, { retry: false });\n const availableProjects = useMemo(\n () => (projectList?.items ?? []).filter((project) => !project.archived),\n [projectList?.items]\n );\n\n const [mode, setMode] = useState<InviteMode>('single');\n const [email, setEmail] = useState('');\n const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);\n const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);\n const [singleError, setSingleError] = useState<string | null>(null);\n const [singleSuccess, setSingleSuccess] = useState<string | null>(null);\n const [bulkError, setBulkError] = useState<string | null>(null);\n const [bulkSuccess, setBulkSuccess] = useState<string | null>(null);\n const [pendingFeedback, setPendingFeedback] = useState<FeedbackState>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [dragActive, setDragActive] = useState(false);\n const [isSubmittingBulk, setIsSubmittingBulk] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [cancelTarget, setCancelTarget] = useState<WorkspaceInvitation | null>(null);\n\n const { mutateAsync: createInvitation, isPending: isCreatingInvitation } =\n useCreateInvitation(workspaceId);\n const { mutateAsync: bulkCreateInvitations, isPending: isCreatingBulkInvites } =\n useBulkCreateInvitations();\n const { mutate: resendInvitation, isPending: isResendingInvitation } =\n useResendInvitation(workspaceId);\n const { mutate: cancelInvitation, isPending: isCancellingInvitation } =\n useCancelInvitation(workspaceId);\n const [invitationActionId, setInvitationActionId] = useState<string | null>(null);\n\n const pendingInvitations = invitationsData?.items ?? [];\n const isMutatingInvite = isCreatingInvitation || isCreatingBulkInvites || isSubmittingBulk;\n const selectedRoleNames = useMemo(\n () =>\n availableRoles.filter((role) => selectedRoleIds.includes(role.id)).map((role) => role.name),\n [availableRoles, selectedRoleIds]\n );\n const selectedProjectNames = useMemo(\n () =>\n availableProjects\n .filter((project) => selectedProjectIds.includes(project.id))\n .map((project) => project.name),\n [availableProjects, selectedProjectIds]\n );\n\n const readyRows = useMemo(\n () => rows.filter((row) => row.errors.length === 0 && row.status.kind !== 'invited'),\n [rows]\n );\n const invalidRows = useMemo(() => rows.filter((row) => row.errors.length > 0), [rows]);\n\n if (!workspaceId) {\n return (\n <div className=\"space-y-4 p-6\">\n <div className=\"rounded-xl border bg-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 ) : invitationsError && pendingInvitations.length === 0 ? (\n <div\n role=\"alert\"\n className=\"space-y-3 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\"\n >\n <p>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingLoadFailed',\n 'Failed to load pending invitations for this workspace.'\n )}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : pendingInvitations.length === 0 ? (\n <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 {invitationsError ? (\n <div\n role=\"alert\"\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-md border border-destructive/20 bg-destructive/10 p-3 text-sm text-destructive\"\n >\n <span>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingStaleWarning',\n 'Pending invitations may be outdated. Try again to refresh this list.'\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : null}\n {pendingInvitations.map((invitation) => {\n const roleNames = getRoleDisplayNames(invitation);\n const projectNames = getProjectDisplayNames(invitation);\n const isRunningAction =\n invitationActionId === invitation.id &&\n (isResendingInvitation || isCancellingInvitation);\n\n return (\n <div key={invitation.id} className=\"rounded-lg border p-4\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-semibold\">{invitation.email}</p>\n <Badge variant=\"outline\">{invitation.status}</Badge>\n </div>\n <div className=\"grid gap-3 text-sm text-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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"}
|
|
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-text-secondary\">\n Choose roles. Hover any role to preview its full description.\n </p>\n </div>\n {selectedRoleIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedRoleIds.length} selected</Badge>\n ) : null}\n </div>\n <TooltipProvider>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingRoles ? (\n <p className=\"text-sm text-text-secondary\">Loading roles…</p>\n ) : availableRoles.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No assignable roles were returned for this workspace.\n </p>\n ) : (\n availableRoles.map((role) => {\n const isSelected = selectedRoleIds.includes(role.id);\n return (\n <Tooltip key={role.id}>\n <TooltipTrigger asChild>\n <label\n className={`flex cursor-pointer items-center justify-between gap-3 rounded-lg border px-3 py-3 text-left text-sm font-medium transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <span className=\"min-w-0 flex-1 truncate\">{role.name}</span>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onRoleToggle(role.id, Boolean(checked))}\n aria-label={`Select ${role.name}`}\n />\n </label>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-xs text-sm leading-6\">\n <p className=\"font-medium\">{role.name}</p>\n <p className=\"mt-1 text-text-secondary\">\n {role.description || 'No description available for this role.'}\n </p>\n {role.isSystem ? <p className=\"mt-2 text-xs\">System role</p> : null}\n </TooltipContent>\n </Tooltip>\n );\n })\n )}\n </div>\n </TooltipProvider>\n </div>\n\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center justify-between gap-2\">\n <div className=\"space-y-1\">\n <h3 className=\"text-base font-semibold\">Projects to add on acceptance (Optional)</h3>\n <p className=\"text-sm text-text-secondary\">\n The invited user will be added to these projects automatically after they accept.\n </p>\n </div>\n {selectedProjectIds.length > 0 ? (\n <Badge variant=\"secondary\">{selectedProjectIds.length} selected</Badge>\n ) : null}\n </div>\n <div className=\"grid grid-cols-1 gap-2 md:grid-cols-2 xl:grid-cols-3\">\n {isLoadingProjects ? (\n <p className=\"text-sm text-text-secondary\">Loading projects…</p>\n ) : availableProjects.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\n No active projects are available in this workspace yet.\n </p>\n ) : (\n availableProjects.map((project) => {\n const isSelected = selectedProjectIds.includes(project.id);\n return (\n <label\n key={project.id}\n className={`flex cursor-pointer items-start justify-between gap-3 rounded-lg border px-3 py-3 transition-colors focus-within:ring-2 focus-within:ring-[var(--color-focus-ring)] focus-within:ring-offset-2 ${\n isSelected\n ? 'border-primary bg-action-primary-bg/10 text-text-primary shadow-sm'\n : 'border-border-subtle bg-bg-surface text-text-primary hover:bg-accent'\n } ${disabled ? 'opacity-70' : ''}`}\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"truncate text-sm font-semibold leading-tight\">{project.name}</p>\n <div className=\"flex items-center gap-2 text-xs text-text-secondary\">\n <span className=\"truncate font-mono\">{project.key}</span>\n <Badge variant=\"outline\">{project.status}</Badge>\n </div>\n </div>\n <Checkbox\n checked={isSelected}\n disabled={disabled}\n onCheckedChange={(checked) => onProjectToggle(project.id, Boolean(checked))}\n aria-label={`Select ${project.name}`}\n />\n </label>\n );\n })\n )}\n </div>\n </div>\n </div>\n );\n}\n\nfunction WorkspaceInvitePageContent() {\n const { workspaceId: routeWorkspaceId } = useParams<{ workspaceId: string }>();\n const { t } = useI18n();\n const navigate = useNavigate();\n const { basePath = '/workspaces', workspaceId: contextWorkspaceId } = useWorkspacesContext();\n const { withCurrentSearch } = useScopedNavigation();\n const workspaceId = routeWorkspaceId || contextWorkspaceId || '';\n\n useWorkspacesPageView('workspaces.invite', { workspaceId: workspaceId || undefined });\n\n const { canInviteMember, isLoading: permissionsLoading } = useWorkspacePermissions();\n const {\n data: workspace,\n isLoading: isLoadingWorkspace,\n error: workspaceError,\n } = useWorkspace(workspaceId);\n const { data: availableRoles = [], isLoading: isLoadingRoles } = useWorkspaceRoles(workspaceId);\n const { data: projectList, isLoading: isLoadingProjects } = useProjects(\n { archived: false },\n { limit: 100 },\n workspaceId\n );\n const {\n data: invitationsData,\n isLoading: isLoadingInvitations,\n isFetching: isFetchingInvitations,\n error: invitationsError,\n refetch: refetchInvitations,\n } = useWorkspaceInvitations(workspaceId, 'PENDING', { limit: 50 }, { retry: false });\n const availableProjects = useMemo(\n () => (projectList?.items ?? []).filter((project) => !project.archived),\n [projectList?.items]\n );\n\n const [mode, setMode] = useState<InviteMode>('single');\n const [email, setEmail] = useState('');\n const [selectedRoleIds, setSelectedRoleIds] = useState<string[]>([]);\n const [selectedProjectIds, setSelectedProjectIds] = useState<string[]>([]);\n const [singleError, setSingleError] = useState<string | null>(null);\n const [singleSuccess, setSingleSuccess] = useState<string | null>(null);\n const [bulkError, setBulkError] = useState<string | null>(null);\n const [bulkSuccess, setBulkSuccess] = useState<string | null>(null);\n const [pendingFeedback, setPendingFeedback] = useState<FeedbackState>(null);\n const [rows, setRows] = useState<PreviewRow[]>([]);\n const [fileName, setFileName] = useState<string | null>(null);\n const [dragActive, setDragActive] = useState(false);\n const [isSubmittingBulk, setIsSubmittingBulk] = useState(false);\n const fileInputRef = useRef<HTMLInputElement>(null);\n const [cancelTarget, setCancelTarget] = useState<WorkspaceInvitation | null>(null);\n\n const { mutateAsync: createInvitation, isPending: isCreatingInvitation } =\n useCreateInvitation(workspaceId);\n const { mutateAsync: bulkCreateInvitations, isPending: isCreatingBulkInvites } =\n useBulkCreateInvitations();\n const { mutate: resendInvitation, isPending: isResendingInvitation } =\n useResendInvitation(workspaceId);\n const { mutate: cancelInvitation, isPending: isCancellingInvitation } =\n useCancelInvitation(workspaceId);\n const [invitationActionId, setInvitationActionId] = useState<string | null>(null);\n\n const pendingInvitations = invitationsData?.items ?? [];\n const isMutatingInvite = isCreatingInvitation || isCreatingBulkInvites || isSubmittingBulk;\n const selectedRoleNames = useMemo(\n () =>\n availableRoles.filter((role) => selectedRoleIds.includes(role.id)).map((role) => role.name),\n [availableRoles, selectedRoleIds]\n );\n const selectedProjectNames = useMemo(\n () =>\n availableProjects\n .filter((project) => selectedProjectIds.includes(project.id))\n .map((project) => project.name),\n [availableProjects, selectedProjectIds]\n );\n\n const readyRows = useMemo(\n () => rows.filter((row) => row.errors.length === 0 && row.status.kind !== 'invited'),\n [rows]\n );\n const invalidRows = useMemo(() => rows.filter((row) => row.errors.length > 0), [rows]);\n\n if (!workspaceId) {\n return (\n <div className=\"space-y-4 p-6\">\n <div className=\"rounded-xl border bg-bg-surface p-8 text-center\">\n <h1 className=\"text-2xl font-bold\">\n {tWithFallback(t, 'workspaceInvite.noWorkspaceTitle', 'Select a workspace first')}\n </h1>\n <p className=\"mt-2 text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.noWorkspaceDescription',\n 'Choose a current workspace from the header or open a workspace detail page before inviting members.'\n )}\n </p>\n <div className=\"mt-4\">\n <Button\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n variant=\"outline\"\n >\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n </div>\n </div>\n </div>\n );\n }\n\n if (permissionsLoading || isLoadingWorkspace) {\n return (\n <div className=\"space-y-6 p-6\">\n <Skeleton className=\"h-10 w-48\" />\n <Skeleton className=\"h-40 w-full\" />\n <Skeleton className=\"h-80 w-full\" />\n </div>\n );\n }\n\n if (!canInviteMember) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"space-y-2 text-center\">\n <h2 className=\"text-lg font-semibold\">\n {tWithFallback(t, 'workspaceInvite.accessDenied', 'Access Denied')}\n </h2>\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.accessDeniedDescription',\n \"You don't have permission to invite members into this workspace.\"\n )}\n </p>\n </div>\n </div>\n );\n }\n\n if (workspaceError || !workspace) {\n return (\n <div className=\"space-y-4 p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/list`))}\n className=\"gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspaces', 'Back to Workspaces')}\n </Button>\n <div className=\"rounded-xl border bg-bg-surface p-8 text-center text-status-error-text\">\n {tWithFallback(\n t,\n 'workspaceInvite.workspaceLoadError',\n 'Unable to load workspace invite details.'\n )}\n </div>\n </div>\n );\n }\n\n const handleRoleToggle = (roleId: string, checked: boolean) => {\n setSelectedRoleIds((prev) =>\n checked\n ? [...prev, roleId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== roleId)\n );\n };\n\n const handleProjectToggle = (projectId: string, checked: boolean) => {\n setSelectedProjectIds((prev) =>\n checked\n ? [...prev, projectId].filter((value, index, array) => array.indexOf(value) === index)\n : prev.filter((id) => id !== projectId)\n );\n };\n\n const handleSingleInvite = async (event: React.FormEvent<HTMLFormElement>) => {\n event.preventDefault();\n setSingleError(null);\n setSingleSuccess(null);\n setPendingFeedback(null);\n\n if (!email.trim()) {\n setSingleError('Email is required.');\n return;\n }\n\n try {\n await createInvitation(\n buildInvitationPayload(\n workspaceId,\n email.trim().toLowerCase(),\n selectedRoleIds,\n selectedProjectIds\n )\n );\n setEmail('');\n setSingleSuccess(`Invitation sent to ${email.trim().toLowerCase()}.`);\n } catch (error) {\n setSingleError(formatInviteError(error));\n }\n };\n\n const handleFile = (file: File) => {\n if (!isCsvFile(file)) {\n setBulkError('Please upload a .csv file.');\n return;\n }\n\n if (file.size > MAX_BULK_INVITE_FILE_SIZE) {\n setBulkError('CSV file is too large (max 1 MB).');\n return;\n }\n\n const reader = new FileReader();\n reader.onload = () => {\n const text = typeof reader.result === 'string' ? reader.result : '';\n const parsed = parseWorkspaceBulkInviteCsv(text);\n const limitedRows = parsed.slice(0, MAX_BULK_INVITE_ROWS);\n setRows(limitedRows.map((row) => ({ ...row, status: { kind: 'idle' } })));\n setFileName(file.name);\n setBulkError(\n parsed.length > MAX_BULK_INVITE_ROWS\n ? `Only the first ${MAX_BULK_INVITE_ROWS} rows were loaded. Split larger CSV files into smaller batches.`\n : null\n );\n setBulkSuccess(null);\n setPendingFeedback(null);\n };\n reader.onerror = () => {\n setBulkError('Failed to read CSV file.');\n };\n reader.readAsText(file);\n };\n\n const handleBulkInvite = async () => {\n if (readyRows.length === 0 || isSubmittingBulk) {\n return;\n }\n\n setBulkError(null);\n setBulkSuccess(null);\n setPendingFeedback(null);\n setIsSubmittingBulk(true);\n setRows((prev) =>\n prev.map((row) =>\n row.errors.length === 0 && row.status.kind !== 'invited'\n ? { ...row, status: { kind: 'sending' } }\n : row\n )\n );\n\n try {\n await bulkCreateInvitations(\n readyRows.map((row) =>\n buildInvitationPayload(workspaceId, row.email, selectedRoleIds, selectedProjectIds)\n )\n );\n\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'invited' } } : row\n )\n );\n setBulkSuccess(\n `Sent ${readyRows.length} workspace invitation${readyRows.length === 1 ? '' : 's'}.`\n );\n } catch (error) {\n const message = formatInviteError(error);\n setRows((prev) =>\n prev.map((row) =>\n row.status.kind === 'sending' ? { ...row, status: { kind: 'failed', message } } : row\n )\n );\n setBulkError(message);\n } finally {\n setIsSubmittingBulk(false);\n }\n };\n\n const handleResendInvitation = (invitation: WorkspaceInvitation) => {\n setPendingFeedback(null);\n setInvitationActionId(invitation.id);\n resendInvitation(invitation.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation resent to ${invitation.email}.`,\n });\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n const handleConfirmCancel = () => {\n if (!cancelTarget) {\n return;\n }\n\n setPendingFeedback(null);\n setInvitationActionId(cancelTarget.id);\n cancelInvitation(cancelTarget.id, {\n onSuccess: () => {\n setPendingFeedback({\n tone: 'success',\n message: `Invitation cancelled for ${cancelTarget.email}.`,\n });\n setCancelTarget(null);\n },\n onError: (error) => {\n setPendingFeedback({ tone: 'error', message: formatInviteError(error) });\n },\n onSettled: () => setInvitationActionId(null),\n });\n };\n\n return (\n <>\n <AlertDialog\n open={Boolean(cancelTarget)}\n onOpenChange={(open) => !open && setCancelTarget(null)}\n >\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Cancel pending invitation?</AlertDialogTitle>\n <AlertDialogDescription>\n {cancelTarget\n ? `This will revoke the pending invite for ${cancelTarget.email}. They will need a new invitation to join later.`\n : 'This will revoke the pending invitation.'}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel disabled={isCancellingInvitation}>Keep invitation</AlertDialogCancel>\n <AlertDialogAction onClick={handleConfirmCancel} disabled={isCancellingInvitation}>\n {isCancellingInvitation ? <Loader2 className=\"size-4 animate-spin\" /> : null}\n Cancel invitation\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n\n <div className=\"space-y-6 p-3 sm:p-6\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(withCurrentSearch(`${basePath}/${workspaceId}`))}\n className=\"-ml-2 mb-2 gap-2\"\n >\n <ArrowLeft size={18} />\n {tWithFallback(t, 'workspaceInvite.backToWorkspace', 'Back to Workspace')}\n </Button>\n <div className=\"flex items-center gap-2\">\n <UserPlus className=\"text-primary\" size={24} />\n <h1 className=\"text-2xl font-bold sm:text-3xl\">\n {tWithFallback(t, 'workspaceInvite.title', 'Invite Members')}\n </h1>\n </div>\n <p className=\"mt-2 text-sm text-text-secondary sm:text-base\">\n {workspace.name} —{' '}\n {tWithFallback(\n t,\n 'workspaceInvite.subtitle',\n 'Invite people into this workspace and optionally grant project access.'\n )}\n </p>\n </div>\n </div>\n\n <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-text-secondary\">\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-text-secondary\">\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-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{singleError}</span>\n </div>\n ) : null}\n {singleSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{singleSuccess}</span>\n </div>\n ) : null}\n\n <form onSubmit={handleSingleInvite} className=\"space-y-4\">\n <div className=\"space-y-2\">\n <label htmlFor=\"workspace-invite-email\" className=\"text-sm font-medium\">\n {tWithFallback(t, 'workspaceInvite.emailLabel', 'Email Address')}\n </label>\n <Input\n id=\"workspace-invite-email\"\n type=\"email\"\n placeholder=\"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-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 shrink-0\" />\n <span>{bulkError}</span>\n </div>\n ) : null}\n {bulkSuccess ? (\n <div className=\"flex items-center gap-2 rounded-md border border-green-500/20 bg-green-500/10 p-3 text-sm text-green-700 dark:text-green-400\">\n <CheckCircle2 className=\"size-4 shrink-0\" />\n <span>{bulkSuccess}</span>\n </div>\n ) : null}\n\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"space-y-1\">\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceInvite.bulkDescription',\n 'Upload a CSV with one email column. All valid rows will receive the currently selected roles and projects.'\n )}\n </p>\n <div className=\"rounded-md border bg-bg-sunken/30 px-3 py-2 text-xs text-text-secondary\">\n <p className=\"font-medium text-text-primary\">Sample format</p>\n <pre className=\"mt-1 whitespace-pre-wrap\">\n email{`\\n`}alice@example.com{`\\n`}bob@example.com\n </pre>\n </div>\n {(projectList?.total ?? 0) > availableProjects.length ? (\n <p className=\"text-xs text-text-secondary\">\n Showing the first {availableProjects.length} active projects. Narrow the\n workspace project list if you need to invite into more.\n </p>\n ) : null}\n </div>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={downloadWorkspaceBulkInviteTemplate}\n className=\"gap-2\"\n >\n <Download size={16} />\n {tWithFallback(t, 'workspaceInvite.downloadTemplate', 'Download template')}\n </Button>\n </div>\n\n <div\n onDragOver={(event) => {\n event.preventDefault();\n setDragActive(true);\n }}\n onDragLeave={() => setDragActive(false)}\n onDrop={(event) => {\n event.preventDefault();\n setDragActive(false);\n const file = event.dataTransfer.files?.[0];\n if (file) {\n handleFile(file);\n } else {\n setBulkError('Please upload a .csv file.');\n }\n }}\n className={`flex flex-col items-center justify-center gap-3 rounded-xl border-2 border-dashed p-8 transition-colors ${\n dragActive ? 'border-primary bg-action-primary-bg/5' : 'border-border-subtle bg-bg-surface'\n }`}\n >\n <Upload size={28} className=\"text-text-secondary\" />\n <div className=\"text-center\">\n <p className=\"text-sm font-medium\">\n {fileName || 'Drop a CSV file here or click to browse'}\n </p>\n <p className=\"mt-1 text-xs text-text-secondary\">\n CSV files only. Required column: email.\n </p>\n </div>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".csv,text/csv\"\n onChange={(event) => {\n const file = event.target.files?.[0];\n if (file) {\n handleFile(file);\n }\n event.target.value = '';\n }}\n className=\"hidden\"\n aria-label=\"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-text-secondary\">\n <span>Total rows: {rows.length}</span>\n <span>Ready: {readyRows.length}</span>\n <span>With errors: {invalidRows.length}</span>\n {invalidRows.length > 0 ? (\n <Badge variant=\"destructive\">Invalid rows will be skipped</Badge>\n ) : null}\n </div>\n {invalidRows.length > 0 ? (\n <div className=\"rounded-md border border-status-error-border/20 bg-status-error-bg/10 px-3 py-2 text-sm text-status-error-text\">\n {invalidRows.length} row{invalidRows.length === 1 ? '' : 's'} need\n attention. Only valid rows can be invited.\n </div>\n ) : null}\n <div className=\"overflow-hidden rounded-xl border\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-bg-sunken/40\">\n <tr>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n #\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary\">\n Email\n </th>\n <th className=\"px-3 py-2 text-left text-xs font-semibold uppercase tracking-wide text-text-secondary\">\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-status-error-bg/5' : undefined}\n >\n <td className=\"px-3 py-2 text-xs text-text-secondary\">\n {row.rowNumber}\n </td>\n <td className=\"px-3 py-2 text-sm\">\n {row.email || (\n <span className=\"italic text-text-secondary\">(empty)</span>\n )}\n </td>\n <td className=\"px-3 py-2 text-xs\">\n {hasParseErrors ? (\n <div className=\"space-y-1 text-status-error-text\">\n {row.errors.map((errorText) => (\n <span\n key={`${row.rowNumber}-${errorText}`}\n className=\"inline-flex items-center gap-1 pr-3\"\n >\n <XCircle size={14} />\n {errorText}\n </span>\n ))}\n </div>\n ) : row.status.kind === 'sending' ? (\n <span className=\"inline-flex items-center gap-1 text-text-secondary\">\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-status-error-text\">\n <XCircle size={14} />\n {row.status.message}\n </span>\n ) : (\n <span className=\"text-text-secondary\">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-text-secondary\">\n Upload a CSV to preview invite rows here before sending.\n </div>\n )}\n\n <div className=\"flex justify-end\">\n <Button\n type=\"button\"\n onClick={handleBulkInvite}\n disabled={readyRows.length === 0 || isCreatingBulkInvites || isSubmittingBulk}\n className=\"gap-2\"\n >\n {isCreatingBulkInvites || isSubmittingBulk ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Send className=\"size-4\" />\n )}\n {tWithFallback(t, 'workspaceInvite.sendBulkButton', 'Send Bulk Invites')}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n </CardContent>\n </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-status-error-border/20 bg-status-error-bg/10 text-status-error-text'\n }`}\n >\n {pendingFeedback.tone === 'success' ? (\n <CheckCircle2 className=\"size-4 shrink-0\" />\n ) : (\n <AlertCircle className=\"size-4 shrink-0\" />\n )}\n <span>{pendingFeedback.message}</span>\n </div>\n ) : null}\n {isLoadingInvitations ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-20 w-full\" />\n <Skeleton className=\"h-20 w-full\" />\n </div>\n ) : invitationsError && pendingInvitations.length === 0 ? (\n <div\n role=\"alert\"\n className=\"space-y-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\"\n >\n <p>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingLoadFailed',\n 'Failed to load pending invitations for this workspace.'\n )}\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : pendingInvitations.length === 0 ? (\n <p className=\"text-sm text-text-secondary\">\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 {invitationsError ? (\n <div\n role=\"alert\"\n className=\"flex flex-wrap items-center justify-between gap-3 rounded-md border border-status-error-border/20 bg-status-error-bg/10 p-3 text-sm text-status-error-text\"\n >\n <span>\n {tWithFallback(\n t,\n 'workspaceInvite.pendingStaleWarning',\n 'Pending invitations may be outdated. Try again to refresh this list.'\n )}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"w-fit\"\n disabled={isFetchingInvitations}\n onClick={() => {\n void refetchInvitations();\n }}\n >\n {isFetchingInvitations\n ? tWithFallback(t, 'workspaceInvite.retrying', 'Retrying…')\n : tWithFallback(t, 'workspaceInvite.tryAgain', 'Try again')}\n </Button>\n </div>\n ) : null}\n {pendingInvitations.map((invitation) => {\n const roleNames = getRoleDisplayNames(invitation);\n const projectNames = getProjectDisplayNames(invitation);\n const isRunningAction =\n invitationActionId === invitation.id &&\n (isResendingInvitation || isCancellingInvitation);\n\n return (\n <div key={invitation.id} className=\"rounded-lg border p-4\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <p className=\"font-semibold\">{invitation.email}</p>\n <Badge variant=\"outline\">{invitation.status}</Badge>\n </div>\n <div className=\"grid gap-3 text-sm text-text-secondary sm:grid-cols-2\">\n <div>\n <p className=\"font-medium text-text-primary\">Received</p>\n <p>{formatDate(invitation.createdAt) ?? '—'}</p>\n </div>\n <div>\n <p className=\"font-medium text-text-primary\">Expires</p>\n <p>{formatDate(invitation.expiresAt) ?? '—'}</p>\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Roles</p>\n <div className=\"flex flex-wrap gap-2\">\n {roleNames.length > 0 ? (\n roleNames.map((roleName, index) => (\n <Badge\n key={`${invitation.id}-${invitation.roleIds[index] ?? roleName}`}\n variant=\"outline\"\n >\n {roleName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n No roles selected\n </span>\n )}\n </div>\n </div>\n <div>\n <p className=\"mb-2 text-sm font-medium text-text-primary\">Projects</p>\n <div className=\"flex flex-wrap gap-2\">\n {projectNames.length > 0 ? (\n projectNames.map((projectName, index) => (\n <Badge\n key={`${invitation.id}-project-${projectName}-${index}`}\n variant=\"secondary\"\n >\n {projectName}\n </Badge>\n ))\n ) : (\n <span className=\"text-sm text-text-secondary\">\n No project access will be granted automatically\n </span>\n )}\n </div>\n </div>\n </div>\n <div className=\"flex gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => handleResendInvitation(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isResendingInvitation ? (\n <RefreshCw className=\"size-4 animate-spin\" />\n ) : (\n <RefreshCw className=\"size-4\" />\n )}\n Resend\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n disabled={isResendingInvitation || isCancellingInvitation}\n onClick={() => setCancelTarget(invitation)}\n className=\"gap-2\"\n >\n {isRunningAction && isCancellingInvitation ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <XCircle className=\"size-4\" />\n )}\n Cancel\n </Button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )}\n </CardContent>\n </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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"}
|
|
@@ -86,7 +86,7 @@ function P() {
|
|
|
86
86
|
children: a(j, "workspaceSettings.title", "Workspace Settings")
|
|
87
87
|
}),
|
|
88
88
|
/* @__PURE__ */ d("p", {
|
|
89
|
-
className: "text-
|
|
89
|
+
className: "text-text-secondary",
|
|
90
90
|
children: a(j, "workspaceSettings.subtitle", "Manage workspace configuration")
|
|
91
91
|
})
|
|
92
92
|
] }),
|
|
@@ -179,7 +179,7 @@ function P() {
|
|
|
179
179
|
P && /* @__PURE__ */ f(m, { children: [/* @__PURE__ */ d(g, { children: /* @__PURE__ */ d(_, { children: a(j, "workspaceSettings.tags", "Tags") }) }), /* @__PURE__ */ f(h, {
|
|
180
180
|
className: "space-y-3",
|
|
181
181
|
children: [/* @__PURE__ */ d("p", {
|
|
182
|
-
className: "text-sm text-
|
|
182
|
+
className: "text-sm text-text-secondary",
|
|
183
183
|
children: a(j, "workspaceSettings.tagsDescription", "Use universal tags to classify and filter this workspace.")
|
|
184
184
|
}), /* @__PURE__ */ d(M, {
|
|
185
185
|
entityType: "Workspace",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceSettingsPage.js","names":[],"sources":["../../src/pages/WorkspaceSettingsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { type TagItem, TagsWidget } from '@burdenoff/fe-libs/tag';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n OptionalFields,\n} from '@burdenoff/fe-libs/ui';\nimport {\n useGetWorkspaceTagsQuery,\n useReplaceWorkspaceTagsMutation,\n type WorkspaceTagAssignmentFieldsFragment,\n} from '../generated/wspace-operations';\nimport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n Input,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { PermissionGate, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useUpdateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nconst updateWorkspaceSchema = z.object({\n name: z.string().min(2).max(100),\n contactEmail: z.string().email().optional().or(z.literal('')),\n domain: z.string().optional(),\n website: z.string().url().optional().or(z.literal('')),\n});\n\ntype UpdateWorkspaceFormData = z.infer<typeof updateWorkspaceSchema>;\n\nexport function WorkspaceSettingsPage() {\n const { t } = useI18n();\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.settings', { workspaceId, requireParams: true });\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { data: workspace, isLoading } = useWorkspace(workspaceId || '');\n const { mutate: updateWorkspace, isPending } = useUpdateWorkspace();\n\n // Workspace tags (universal-tags assignments) — backend exposed via\n // wspace-workspace-svc workspace-tags.gql; this UI is the canonical\n // editor for them. Identical pattern to ProjectDetailPage.\n const { data: tagsData, refetch: refetchTags } = useGetWorkspaceTagsQuery({\n variables: { input: { workspaceId: workspaceId ?? '' } },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const [replaceWorkspaceTags, { loading: tagsUpdating }] = useReplaceWorkspaceTagsMutation();\n // The widget keys tags by `id` for add/remove. We store the\n // assignment's tagId there so the round-trip back to\n // replaceWorkspaceTags has the right value to send. Display fields\n // come from the embedded `tag: UniversalTag` resolved by the\n // assignment — assignments without a hydrated tag (deleted or\n // unresolved) are dropped so the widget never renders empty badges.\n const widgetTags = useMemo<TagItem[]>(() => {\n const assignments = tagsData?.getWorkspaceTags ?? [];\n return assignments.flatMap((a: WorkspaceTagAssignmentFieldsFragment) => {\n if (!a.tag) return [];\n return [\n {\n id: a.tagId,\n key: a.tag.key,\n label: a.tag.label,\n value: a.tag.value ?? null,\n vocabularyId: a.tag.vocabularyId ?? null,\n parentId: a.tag.parentId ?? null,\n hierarchyPath: a.tag.hierarchyPath ?? null,\n color: a.tag.color ?? null,\n icon: a.tag.icon ?? null,\n description: a.tag.description ?? null,\n } satisfies TagItem,\n ];\n });\n }, [tagsData?.getWorkspaceTags]);\n\n const persistTags = async (next: TagItem[]) => {\n if (!workspaceId) return;\n await replaceWorkspaceTags({\n variables: {\n input: {\n workspaceId,\n tagIds: next.map((t) => t.id),\n },\n },\n });\n await refetchTags();\n };\n\n const handleAddTag = (tag: TagItem) => persistTags([...widgetTags, tag]);\n const handleRemoveTag = (tagId: string) => persistTags(widgetTags.filter((t) => t.id !== tagId));\n\n const handleSubmit = (data: UpdateWorkspaceFormData) => {\n if (!workspaceId) return;\n\n updateWorkspace(\n {\n id: workspaceId,\n input: {\n ...data,\n contactEmail: data.contactEmail || undefined,\n domain: data.domain || undefined,\n website: data.website || undefined,\n },\n },\n {\n onSuccess: () => {\n navigate(`${basePath}/${workspaceId}`);\n },\n }\n );\n };\n\n if (isLoading) {\n return <div className=\"p-6\">{tWithFallback(t, 'workspaceSettings.loading', 'Loading...')}</div>;\n }\n\n if (!workspace) {\n return (\n <div className=\"p-6\">\n {tWithFallback(t, 'workspaceSettings.notFound', 'Workspace not found')}\n </div>\n );\n }\n\n return (\n <PermissionGate\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_UPDATE}\n fallback={\n <div className=\"p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-6 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'workspaceSettings.permissionRequired', 'Permission Required')}\n description={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDesc',\n \"You don't have permission to edit workspace settings.\"\n )}\n details={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDetails',\n 'Required: workspace:update'\n )}\n primaryAction={{\n label: tWithFallback(t, 'workspaceSettings.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${workspaceId}`),\n }}\n />\n </div>\n }\n >\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspaceSettings.title', 'Workspace Settings')}\n </h1>\n <p className=\"text-muted-foreground\">\n {tWithFallback(t, 'workspaceSettings.subtitle', 'Manage workspace configuration')}\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceSettings.generalSettings', 'General Settings')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Form\n schema={updateWorkspaceSchema}\n onSubmit={handleSubmit}\n formOptions={{\n defaultValues: {\n name: workspace?.name || '',\n contactEmail: workspace?.contactEmail || '',\n domain: workspace?.domain || '',\n website: workspace?.website || '',\n },\n }}\n >\n {(form) => (\n <div className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.workspaceName', 'Workspace Name')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"contactEmail\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.contactEmail', 'Contact Email')}\n </FormLabel>\n <FormControl>\n <Input type=\"email\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <OptionalFields\n title={tWithFallback(t, 'workspaceSettings.advancedTitle', 'Advanced settings')}\n description={tWithFallback(\n t,\n 'workspaceSettings.advancedDescription',\n 'Custom domain + website. Most workspaces leave these blank.'\n )}\n >\n <FormField\n control={form.control}\n name=\"domain\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.domain', 'Domain')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"website\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.website', 'Website')}\n </FormLabel>\n <FormControl>\n <Input type=\"url\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </OptionalFields>\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" disabled={isPending} className=\"gap-2\">\n <Save size={20} />\n {isPending\n ? tWithFallback(t, 'workspaceSettings.saving', 'Saving...')\n : tWithFallback(t, 'workspaceSettings.saveChanges', 'Save Changes')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n >\n {tWithFallback(t, 'workspaceSettings.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n )}\n </Form>\n </CardContent>\n </Card>\n\n {workspaceId && (\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'workspaceSettings.tags', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-muted-foreground\">\n {tWithFallback(\n t,\n 'workspaceSettings.tagsDescription',\n 'Use universal tags to classify and filter this workspace.'\n )}\n </p>\n <TagsWidget\n entityType=\"Workspace\"\n entityId={workspaceId}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n syncGraphNotifications={false}\n className={tagsUpdating ? 'pointer-events-none opacity-75' : undefined}\n />\n </CardContent>\n </Card>\n )}\n </div>\n </PermissionGate>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceSettingsPage.js","names":[],"sources":["../../src/pages/WorkspaceSettingsPage.tsx"],"sourcesContent":["import { useMemo } from 'react';\nimport { useParams, useNavigate } from 'react-router-dom';\nimport { ArrowLeft, Save } from 'lucide-react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { type TagItem, TagsWidget } from '@burdenoff/fe-libs/tag';\nimport { tWithFallback } from '../utils/i18n';\nimport {\n Button,\n Card,\n CardHeader,\n CardTitle,\n CardContent,\n OptionalFields,\n} from '@burdenoff/fe-libs/ui';\nimport {\n useGetWorkspaceTagsQuery,\n useReplaceWorkspaceTagsMutation,\n type WorkspaceTagAssignmentFieldsFragment,\n} from '../generated/wspace-operations';\nimport {\n Form,\n FormField,\n FormItem,\n FormLabel,\n FormControl,\n FormMessage,\n Input,\n z,\n} from '@burdenoff/fe-libs/form';\nimport { PermissionGate, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { WORKSPACE_PERMISSIONS } from '../constants/permissions';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useUpdateWorkspace } from '../hooks/useWorkspaceMutations';\nimport { useWorkspacesContext } from '../providers/WorkspacesProvider';\nimport { useWorkspacesPageView } from '../hooks/useWorkspacesPageView';\n\nconst updateWorkspaceSchema = z.object({\n name: z.string().min(2).max(100),\n contactEmail: z.string().email().optional().or(z.literal('')),\n domain: z.string().optional(),\n website: z.string().url().optional().or(z.literal('')),\n});\n\ntype UpdateWorkspaceFormData = z.infer<typeof updateWorkspaceSchema>;\n\nexport function WorkspaceSettingsPage() {\n const { t } = useI18n();\n const { workspaceId } = useParams<{ workspaceId: string }>();\n useWorkspacesPageView('workspaces.settings', { workspaceId, requireParams: true });\n const navigate = useNavigate();\n const { basePath = '/' } = useWorkspacesContext();\n const { data: workspace, isLoading } = useWorkspace(workspaceId || '');\n const { mutate: updateWorkspace, isPending } = useUpdateWorkspace();\n\n // Workspace tags (universal-tags assignments) — backend exposed via\n // wspace-workspace-svc workspace-tags.gql; this UI is the canonical\n // editor for them. Identical pattern to ProjectDetailPage.\n const { data: tagsData, refetch: refetchTags } = useGetWorkspaceTagsQuery({\n variables: { input: { workspaceId: workspaceId ?? '' } },\n skip: !workspaceId,\n fetchPolicy: 'cache-and-network',\n });\n const [replaceWorkspaceTags, { loading: tagsUpdating }] = useReplaceWorkspaceTagsMutation();\n // The widget keys tags by `id` for add/remove. We store the\n // assignment's tagId there so the round-trip back to\n // replaceWorkspaceTags has the right value to send. Display fields\n // come from the embedded `tag: UniversalTag` resolved by the\n // assignment — assignments without a hydrated tag (deleted or\n // unresolved) are dropped so the widget never renders empty badges.\n const widgetTags = useMemo<TagItem[]>(() => {\n const assignments = tagsData?.getWorkspaceTags ?? [];\n return assignments.flatMap((a: WorkspaceTagAssignmentFieldsFragment) => {\n if (!a.tag) return [];\n return [\n {\n id: a.tagId,\n key: a.tag.key,\n label: a.tag.label,\n value: a.tag.value ?? null,\n vocabularyId: a.tag.vocabularyId ?? null,\n parentId: a.tag.parentId ?? null,\n hierarchyPath: a.tag.hierarchyPath ?? null,\n color: a.tag.color ?? null,\n icon: a.tag.icon ?? null,\n description: a.tag.description ?? null,\n } satisfies TagItem,\n ];\n });\n }, [tagsData?.getWorkspaceTags]);\n\n const persistTags = async (next: TagItem[]) => {\n if (!workspaceId) return;\n await replaceWorkspaceTags({\n variables: {\n input: {\n workspaceId,\n tagIds: next.map((t) => t.id),\n },\n },\n });\n await refetchTags();\n };\n\n const handleAddTag = (tag: TagItem) => persistTags([...widgetTags, tag]);\n const handleRemoveTag = (tagId: string) => persistTags(widgetTags.filter((t) => t.id !== tagId));\n\n const handleSubmit = (data: UpdateWorkspaceFormData) => {\n if (!workspaceId) return;\n\n updateWorkspace(\n {\n id: workspaceId,\n input: {\n ...data,\n contactEmail: data.contactEmail || undefined,\n domain: data.domain || undefined,\n website: data.website || undefined,\n },\n },\n {\n onSuccess: () => {\n navigate(`${basePath}/${workspaceId}`);\n },\n }\n );\n };\n\n if (isLoading) {\n return <div className=\"p-6\">{tWithFallback(t, 'workspaceSettings.loading', 'Loading...')}</div>;\n }\n\n if (!workspace) {\n return (\n <div className=\"p-6\">\n {tWithFallback(t, 'workspaceSettings.notFound', 'Workspace not found')}\n </div>\n );\n }\n\n return (\n <PermissionGate\n permission={WORKSPACE_PERMISSIONS.WORKSPACE_UPDATE}\n fallback={\n <div className=\"p-6\">\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-6 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <AccessDenied\n variant=\"card\"\n title={tWithFallback(t, 'workspaceSettings.permissionRequired', 'Permission Required')}\n description={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDesc',\n \"You don't have permission to edit workspace settings.\"\n )}\n details={tWithFallback(\n t,\n 'workspaceSettings.permissionRequiredDetails',\n 'Required: workspace:update'\n )}\n primaryAction={{\n label: tWithFallback(t, 'workspaceSettings.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${workspaceId}`),\n }}\n />\n </div>\n }\n >\n <div className=\"space-y-4 sm:space-y-6 p-3 sm:p-6\">\n <div>\n <Button\n variant=\"ghost\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n className=\"gap-2 mb-2 -ml-2\"\n >\n <ArrowLeft size={20} />\n {tWithFallback(t, 'workspaceSettings.backToWorkspace', 'Back to Workspace')}\n </Button>\n <h1 className=\"text-2xl sm:text-3xl font-bold\">\n {tWithFallback(t, 'workspaceSettings.title', 'Workspace Settings')}\n </h1>\n <p className=\"text-text-secondary\">\n {tWithFallback(t, 'workspaceSettings.subtitle', 'Manage workspace configuration')}\n </p>\n </div>\n\n <Card>\n <CardHeader>\n <CardTitle>\n {tWithFallback(t, 'workspaceSettings.generalSettings', 'General Settings')}\n </CardTitle>\n </CardHeader>\n <CardContent>\n <Form\n schema={updateWorkspaceSchema}\n onSubmit={handleSubmit}\n formOptions={{\n defaultValues: {\n name: workspace?.name || '',\n contactEmail: workspace?.contactEmail || '',\n domain: workspace?.domain || '',\n website: workspace?.website || '',\n },\n }}\n >\n {(form) => (\n <div className=\"space-y-4\">\n <FormField\n control={form.control}\n name=\"name\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.workspaceName', 'Workspace Name')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"contactEmail\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.contactEmail', 'Contact Email')}\n </FormLabel>\n <FormControl>\n <Input type=\"email\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <OptionalFields\n title={tWithFallback(t, 'workspaceSettings.advancedTitle', 'Advanced settings')}\n description={tWithFallback(\n t,\n 'workspaceSettings.advancedDescription',\n 'Custom domain + website. Most workspaces leave these blank.'\n )}\n >\n <FormField\n control={form.control}\n name=\"domain\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.domain', 'Domain')}\n </FormLabel>\n <FormControl>\n <Input {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n\n <FormField\n control={form.control}\n name=\"website\"\n render={({ field }) => (\n <FormItem>\n <FormLabel>\n {tWithFallback(t, 'workspaceSettings.website', 'Website')}\n </FormLabel>\n <FormControl>\n <Input type=\"url\" {...field} />\n </FormControl>\n <FormMessage />\n </FormItem>\n )}\n />\n </OptionalFields>\n\n <div className=\"flex gap-2\">\n <Button type=\"submit\" disabled={isPending} className=\"gap-2\">\n <Save size={20} />\n {isPending\n ? tWithFallback(t, 'workspaceSettings.saving', 'Saving...')\n : tWithFallback(t, 'workspaceSettings.saveChanges', 'Save Changes')}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => navigate(`${basePath}/${workspaceId}`)}\n >\n {tWithFallback(t, 'workspaceSettings.cancel', 'Cancel')}\n </Button>\n </div>\n </div>\n )}\n </Form>\n </CardContent>\n </Card>\n\n {workspaceId && (\n <Card>\n <CardHeader>\n <CardTitle>{tWithFallback(t, 'workspaceSettings.tags', 'Tags')}</CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tWithFallback(\n t,\n 'workspaceSettings.tagsDescription',\n 'Use universal tags to classify and filter this workspace.'\n )}\n </p>\n <TagsWidget\n entityType=\"Workspace\"\n entityId={workspaceId}\n workspaceId={workspaceId}\n tags={widgetTags}\n onAdd={handleAddTag}\n onRemove={handleRemoveTag}\n syncGraphNotifications={false}\n className={tagsUpdating ? 'pointer-events-none opacity-75' : undefined}\n />\n </CardContent>\n </Card>\n )}\n </div>\n </PermissionGate>\n );\n}\n"],"mappings":"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"}
|
|
@@ -42,10 +42,10 @@ function O() {
|
|
|
42
42
|
children: /* @__PURE__ */ y("div", {
|
|
43
43
|
className: "flex flex-col items-center justify-center py-12",
|
|
44
44
|
children: [/* @__PURE__ */ v("p", {
|
|
45
|
-
className: "text-
|
|
45
|
+
className: "text-status-error-text text-lg font-semibold",
|
|
46
46
|
children: s(O, "workspacesList.loadError", "Error loading workspaces")
|
|
47
47
|
}), /* @__PURE__ */ v("p", {
|
|
48
|
-
className: "text-
|
|
48
|
+
className: "text-text-secondary text-sm mt-2",
|
|
49
49
|
children: W instanceof Error ? W.message : s(O, "workspacesList.unknownError", "Unknown error")
|
|
50
50
|
})]
|
|
51
51
|
})
|
|
@@ -59,7 +59,7 @@ function O() {
|
|
|
59
59
|
className: "text-2xl sm:text-3xl font-bold",
|
|
60
60
|
children: s(O, "workspacesList.title", "Workspaces")
|
|
61
61
|
}), /* @__PURE__ */ v("p", {
|
|
62
|
-
className: "text-sm sm:text-base text-
|
|
62
|
+
className: "text-sm sm:text-base text-text-secondary",
|
|
63
63
|
children: s(O, "workspacesList.description", "Manage and organize your workspaces")
|
|
64
64
|
})] }), /* @__PURE__ */ y("div", {
|
|
65
65
|
className: "flex items-center gap-2",
|
|
@@ -137,7 +137,7 @@ function O() {
|
|
|
137
137
|
className: "relative",
|
|
138
138
|
"data-tour": "workspaces-search-input",
|
|
139
139
|
children: [/* @__PURE__ */ v(h, {
|
|
140
|
-
className: "absolute left-3 top-1/2 -translate-y-1/2 text-
|
|
140
|
+
className: "absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary",
|
|
141
141
|
size: 20
|
|
142
142
|
}), /* @__PURE__ */ v(S, {
|
|
143
143
|
placeholder: s(O, "workspacesList.searchPlaceholder", "Search workspaces..."),
|
|
@@ -167,10 +167,10 @@ function O() {
|
|
|
167
167
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
168
168
|
children: [
|
|
169
169
|
/* @__PURE__ */ v("div", {
|
|
170
|
-
className: "rounded-full bg-
|
|
170
|
+
className: "rounded-full bg-bg-sunken p-6 mb-4",
|
|
171
171
|
children: /* @__PURE__ */ v(d, {
|
|
172
172
|
size: 48,
|
|
173
|
-
className: "text-
|
|
173
|
+
className: "text-text-secondary"
|
|
174
174
|
})
|
|
175
175
|
}),
|
|
176
176
|
/* @__PURE__ */ v("h3", {
|
|
@@ -178,7 +178,7 @@ function O() {
|
|
|
178
178
|
children: s(O, "workspacesList.emptyTitle", "No workspaces found")
|
|
179
179
|
}),
|
|
180
180
|
/* @__PURE__ */ v("p", {
|
|
181
|
-
className: "text-
|
|
181
|
+
className: "text-text-secondary mb-4 max-w-sm",
|
|
182
182
|
children: I.search ? s(O, "workspacesList.emptySearchHint", "Try adjusting your search criteria") : s(O, "workspacesList.emptyCreateHint", "Get started by creating your first workspace")
|
|
183
183
|
}),
|
|
184
184
|
!I.search && /* @__PURE__ */ v(E, {
|
|
@@ -199,7 +199,7 @@ function O() {
|
|
|
199
199
|
className: "grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-4",
|
|
200
200
|
children: Y?.items.map((e) => /* @__PURE__ */ v(a, { workspace: e }, e.id))
|
|
201
201
|
}), Y && Y.total > 0 && /* @__PURE__ */ y("div", {
|
|
202
|
-
className: "flex items-center justify-between text-sm text-
|
|
202
|
+
className: "flex items-center justify-between text-sm text-text-secondary",
|
|
203
203
|
children: [/* @__PURE__ */ y("p", { children: [
|
|
204
204
|
s(O, "workspacesList.showing", "Showing"),
|
|
205
205
|
" ",
|