@burdenoff/microfe-organization 2026.525.3 → 2026.527.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/organization-management/AdminOrganizationRoutes.js +1 -1
  2. package/dist/organization-management/AdminOrganizationRoutes.js.map +1 -1
  3. package/dist/organization-management/UserOrganizationRoutes.js +1 -1
  4. package/dist/organization-management/UserOrganizationRoutes.js.map +1 -1
  5. package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -0
  6. package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
  7. package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -0
  8. package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
  9. package/dist/organization-management/modules/members/components/PendingInvitesList.js +2 -0
  10. package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
  11. package/dist/organization-management/modules/members/pages/InviteMemberPage.js +1 -0
  12. package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
  13. package/dist/organization-management/modules/members/pages/MemberDetailPage.js +2 -1
  14. package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
  15. package/dist/organization-management/modules/members/pages/MembersListPage.js +10 -2
  16. package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
  17. package/dist/organization-management/modules/organizations/components/OrganizationForm.js +6 -0
  18. package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
  19. package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js.map +1 -1
  20. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +1 -0
  21. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
  22. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +6 -1
  23. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
  24. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +1 -0
  25. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
  26. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +15 -4
  27. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
  28. package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js.map +1 -1
  29. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +5 -5
  30. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
  31. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +6 -2
  32. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
  33. package/dist/organization-management/modules/workspaces/hooks/useWorkspaceMembers.js.map +1 -1
  34. package/dist/organization-management/modules/workspaces/hooks/useWorkspaces.js.map +1 -1
  35. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +1 -0
  36. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
  37. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +8 -1
  38. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
  39. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +1 -0
  40. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
  41. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +10 -0
  42. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
  43. package/dist/organization-management/providers/OrgConsoleProvider.js +1 -1
  44. package/dist/organization-management/providers/OrgConsoleProvider.js.map +1 -1
  45. package/dist/organization-management/shared/components/dialog/ConfirmDialog.js +25 -86
  46. package/dist/organization-management/shared/components/dialog/ConfirmDialog.js.map +1 -1
  47. package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js +69 -0
  48. package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js.map +1 -0
  49. package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -0
  50. package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
  51. package/dist/organization-management/shared/components/feedback/ErrorState.js +2 -1
  52. package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
  53. package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -2
  54. package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
  55. package/dist/organization-management/shared/components/input/SearchInput.js +1 -0
  56. package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
  57. package/dist/organization-management/shared/components/input/SelectionCheckbox.js +1 -1
  58. package/dist/organization-management/shared/components/input/SelectionCheckbox.js.map +1 -1
  59. package/dist/organization-management/shared/types/graphql.js.map +1 -1
  60. package/package.json +2 -1
@@ -1 +1 @@
1
- {"version":3,"file":"MembersListPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MembersListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Eye, Mail, RefreshCw, Shield, Trash2 } from 'lucide-react';\nimport { useParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlagDetails } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useMembers, useRemoveMember } from '../hooks/useMembers';\nimport { MemberRoleBadge } from '../components/MemberRoleBadge';\nimport { SearchInput } from '../../../shared/components/input/SearchInput';\nimport { SelectionCheckbox } from '../../../shared/components/input/SelectionCheckbox';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useConfirmDialog } from '../../../shared/components/dialog/ConfirmDialog';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nfunction useFormatDate() {\n const tr = useTr();\n return (date?: string) => {\n if (!date) return tr('common.status.pending', 'Pending');\n return new Date(date).toLocaleDateString();\n };\n}\n\nexport function MembersListPage() {\n const tr = useTr();\n const formatDate = useFormatDate();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n // Follow the platform default-allow feature flag policy: unseeded flags must\n // not disable shipped invite actions, while explicit false still gates them.\n const inviteDetails = useFeatureFlagDetails('organization.invite');\n const inviteEnabled = inviteDetails ? Boolean(inviteDetails.value) : true;\n const { organizationId } = useParams<{ organizationId: string }>();\n const [searchQuery, setSearchQuery] = useState('');\n const debouncedSearchQuery = useDebouncedValue(searchQuery, 250);\n const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(new Set());\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n const { members, loading, error, refetch } = useMembers(organizationId ?? '', 50, 0);\n const { removeMember, loading: deleteLoading } = useRemoveMember();\n\n const filteredMembers = useMemo(() => {\n if (!debouncedSearchQuery) {\n return members;\n }\n\n const normalizedQuery = debouncedSearchQuery.toLowerCase();\n return members.filter(\n (member) =>\n member.userId.toLowerCase().includes(normalizedQuery) ||\n member.role.toLowerCase().includes(normalizedQuery)\n );\n }, [debouncedSearchQuery, members]);\n\n useEffect(() => {\n setSelectedMemberIds((previousSelection) => {\n const visibleIds = new Set(filteredMembers.map((member) => member.id));\n const nextSelection = new Set(\n [...previousSelection].filter((memberId) => visibleIds.has(memberId))\n );\n\n if (nextSelection.size === previousSelection.size) {\n return previousSelection;\n }\n\n return nextSelection;\n });\n }, [filteredMembers]);\n\n const selectedMembers = useMemo(\n () => filteredMembers.filter((member) => selectedMemberIds.has(member.id)),\n [filteredMembers, selectedMemberIds]\n );\n\n const allVisibleSelected =\n filteredMembers.length > 0 &&\n filteredMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelected =\n filteredMembers.some((member) => selectedMemberIds.has(member.id)) && !allVisibleSelected;\n\n const executeRemoveMembers = async (userIds: string[]) => {\n if (!organizationId) return;\n\n try {\n const results = await Promise.allSettled(\n userIds.map(async (userId) => {\n const result = await removeMember(organizationId, userId);\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to remove member');\n }\n })\n );\n\n const rejectedResult = results.find((result) => result.status === 'rejected');\n if (rejectedResult?.status === 'rejected') {\n throw rejectedResult.reason;\n }\n\n setSelectedMemberIds(new Set());\n await refetch();\n toast.success(\n userIds.length === 1\n ? tr('members.remove.successSingle', 'Member removed successfully')\n : `${userIds.length} ${tr('members.remove.successMany', 'members removed successfully')}`\n );\n } catch (removeError) {\n toast.error(\n removeError instanceof Error\n ? removeError.message\n : tr('members.remove.failed', 'Failed to remove member')\n );\n }\n };\n\n const handleRemove = (userId: string) => {\n openConfirmDialog({\n title: tr('members.remove.title', 'Remove member'),\n description: `${tr('members.remove.descriptionPrefix', 'Remove')} ${userId} ${tr('members.remove.descriptionSuffix', 'from this organization? Workspace access inherited from this membership will be impacted.')}`,\n confirmText: tr('members.remove.confirm', 'Remove'),\n variant: 'danger',\n onConfirm: () => {\n void executeRemoveMembers([userId]);\n },\n });\n };\n\n const handleBulkRemove = () => {\n if (selectedMembers.length === 0) return;\n\n openConfirmDialog({\n title: tr('members.remove.bulkTitle', 'Remove selected members'),\n description: `${tr('members.remove.bulkPrefix', 'Remove')} ${selectedMembers.length} ${tr(\n selectedMembers.length === 1\n ? 'members.remove.bulkSuffixSingle'\n : 'members.remove.bulkSuffixMany',\n selectedMembers.length === 1\n ? 'selected member from this organization?'\n : 'selected members from this organization?'\n )}`,\n confirmText: `${tr('members.remove.confirm', 'Remove')} ${selectedMembers.length}`,\n variant: 'danger',\n onConfirm: () => {\n void executeRemoveMembers(selectedMembers.map((member) => member.userId));\n },\n });\n };\n\n const toggleMemberSelection = (memberId: string, checked: boolean) => {\n setSelectedMemberIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n if (checked) {\n nextSelection.add(memberId);\n } else {\n nextSelection.delete(memberId);\n }\n return nextSelection;\n });\n };\n\n const toggleAllMembers = (checked: boolean) => {\n setSelectedMemberIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n filteredMembers.forEach((member) => {\n if (checked) {\n nextSelection.add(member.id);\n } else {\n nextSelection.delete(member.id);\n }\n });\n return nextSelection;\n });\n };\n\n if (!organizationId) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={tr('members.list.missingOrg', 'Organization ID is required to view members')}\n action={{\n label: tr('members.list.goToOrganizations', 'Go to Organizations'),\n onClick: () => navigate(basePath),\n }}\n />\n </div>\n );\n }\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.MEMBER_LIST)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.list.noPermission', \"You don't have permission to view members.\")}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState message={tr('members.list.loading', 'Loading members...')} />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={error.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetch();\n },\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n <div className=\"mx-auto flex w-full max-w-7xl flex-col gap-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-2\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.list.title', 'Members')}\n </h1>\n <p className=\"max-w-2xl text-[var(--color-text-secondary)]\">\n {tr(\n 'members.list.subtitle',\n 'Manage organization members, inspect their roles, and invite more people into this organization.'\n )}\n </p>\n </div>\n\n {inviteEnabled && hasPermission(PERMISSIONS.MEMBER_INVITE) && (\n <button\n onClick={() => navigate(`${basePath}/${organizationId}/members/invite`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Mail size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">{tr('members.invite.button', 'Invite Member')}</span>\n </button>\n )}\n </div>\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('members.list.searchPlaceholder', 'Search by member ID or role...')}\n label={tr('members.list.searchLabel', 'Search organization members')}\n inputId=\"organization-members-search\"\n className=\"flex-1\"\n />\n\n <button\n onClick={() => {\n void refetch();\n }}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-4 py-2 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-95\"\n aria-label={tr('members.list.refresh', 'Refresh members')}\n title={tr('members.list.refresh', 'Refresh members')}\n >\n <RefreshCw size={18} aria-hidden=\"true\" />\n </button>\n </div>\n\n {filteredMembers.length > 0 ? (\n <div className=\"mt-4 flex flex-col gap-3 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-[var(--color-text-primary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllMembers}\n ariaLabel={tr('members.list.selectAll', 'Select all members')}\n />\n <span>{tr('common.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n {selectedMemberIds.size > 0 ? (\n <button\n onClick={() => setSelectedMemberIds(new Set())}\n className=\"text-sm text-[var(--color-text-secondary)] transition-colors hover:text-[var(--color-text-primary)]\"\n >\n {tr('common.clearSelection', 'Clear selection')}\n </button>\n ) : null}\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <span className=\"text-sm text-[var(--color-text-secondary)]\">\n {selectedMemberIds.size} {tr('common.selected', 'selected')}\n </span>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button\n onClick={handleBulkRemove}\n disabled={selectedMembers.length === 0 || deleteLoading}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <span>{tr('common.removeSelected', 'Remove Selected')}</span>\n {selectedMembers.length > 0 ? <span>({selectedMembers.length})</span> : null}\n </button>\n )}\n </div>\n </div>\n ) : null}\n </div>\n\n {filteredMembers.length === 0 ? (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-6 shadow-[var(--shadow-elevation-1)]\">\n <EmptyState\n icon={<Shield size={40} aria-hidden=\"true\" />}\n title={tr('members.list.emptyTitle', 'No members found')}\n description={tr(\n 'members.list.emptyDescription',\n 'Invite someone to this organization to grant them access and assign their role.'\n )}\n action={\n inviteEnabled && hasPermission(PERMISSIONS.MEMBER_INVITE)\n ? {\n label: tr('members.invite.button', 'Invite Member'),\n onClick: () => navigate(`${basePath}/${organizationId}/members/invite`),\n }\n : undefined\n }\n />\n </div>\n ) : (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {filteredMembers.map((member) => (\n <article\n key={member.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-4 shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"flex items-start gap-3\">\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n onChange={(checked) => toggleMemberSelection(member.id, checked)}\n ariaLabel={`${tr('members.list.selectMember', 'Select member')} ${member.userId}`}\n />\n\n <div className=\"flex min-w-0 flex-1 gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Shield size={18} aria-hidden=\"true\" />\n </div>\n\n <div className=\"min-w-0 flex-1 space-y-3\">\n <div>\n <div className=\"flex flex-wrap items-start gap-2\">\n <h2 className=\"break-all text-base font-semibold text-[var(--color-text-primary)]\">\n {member.userId}\n </h2>\n <MemberRoleBadge role={member.role} />\n </div>\n <p className=\"mt-1 break-all font-mono text-xs text-[var(--color-text-muted)]\">\n {tr('members.list.memberId', 'Member ID')}: {member.id}\n </p>\n </div>\n\n <dl className=\"grid grid-cols-1 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-[var(--color-text-primary)]\">\n {member.invitedBy || tr('common.notAvailable', 'N/A')}\n </dd>\n </div>\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </dt>\n <dd className=\"mt-1 text-[var(--color-text-primary)]\">\n {formatDate(member.joinedAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-2 border-t border-[var(--color-border-default)] pt-4\">\n <button\n onClick={() =>\n navigate(`${basePath}/${organizationId}/members/${member.userId}`)\n }\n className=\"inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-border-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n >\n <Eye size={16} aria-hidden=\"true\" />\n {tr('common.view', 'View')}\n </button>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button\n onClick={() => handleRemove(member.userId)}\n disabled={deleteLoading}\n className=\"inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-sm text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n {tr('members.remove.confirm', 'Remove')}\n </button>\n )}\n </div>\n </article>\n ))}\n </div>\n\n <div className=\"hidden overflow-hidden rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)] md:block\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-[var(--color-bg-subtle)]\">\n <tr>\n <th className=\"w-12 px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllMembers}\n ariaLabel={tr('members.list.selectAll', 'Select all members')}\n />\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.user', 'User')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </th>\n <th className=\"px-6 py-3 text-right text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-[var(--color-border-default)]\">\n {filteredMembers.map((member) => (\n <tr\n key={member.id}\n className=\"transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <td className=\"px-6 py-4 align-top\">\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n onChange={(checked) => toggleMemberSelection(member.id, checked)}\n ariaLabel={`${tr('members.list.selectMember', 'Select member')} ${member.userId}`}\n />\n </td>\n <td className=\"px-6 py-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex h-10 w-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Shield size={18} aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"break-all font-medium text-[var(--color-text-primary)]\">\n {member.userId}\n </p>\n <p className=\"break-all font-mono text-xs text-[var(--color-text-muted)]\">\n {tr('common.idLabel', 'ID')}: {member.id}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-6 py-4 align-top\">\n <MemberRoleBadge role={member.role} />\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {member.invitedBy || tr('common.notAvailable', 'N/A')}\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {formatDate(member.joinedAt)}\n </td>\n <td className=\"px-6 py-4 align-top\">\n <div className=\"flex items-center justify-end gap-2\">\n <button\n onClick={() =>\n navigate(`${basePath}/${organizationId}/members/${member.userId}`)\n }\n className=\"rounded-lg p-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-bg-muted)] hover:text-[var(--color-text-primary)]\"\n aria-label={`${tr('members.list.viewMember', 'View member')} ${member.userId}`}\n title={`${tr('members.list.viewMember', 'View member')} ${member.userId}`}\n >\n <Eye size={18} aria-hidden=\"true\" />\n </button>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button\n onClick={() => handleRemove(member.userId)}\n disabled={deleteLoading}\n className=\"rounded-lg p-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label={`${tr('members.list.removeMember', 'Remove member')} ${member.userId}`}\n title={`${tr('members.list.removeMember', 'Remove member')} ${member.userId}`}\n >\n <Trash2 size={18} aria-hidden=\"true\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n )}\n </div>\n\n {ConfirmDialog}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MembersListPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MembersListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Eye, Mail, RefreshCw, Shield, Trash2 } from 'lucide-react';\nimport { useParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useFeatureFlagDetails } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useMembers, useRemoveMember } from '../hooks/useMembers';\nimport { MemberRoleBadge } from '../components/MemberRoleBadge';\nimport { SearchInput } from '../../../shared/components/input/SearchInput';\nimport { SelectionCheckbox } from '../../../shared/components/input/SelectionCheckbox';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useConfirmDialog } from '../../../shared/components/dialog/ConfirmDialog';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\nfunction useFormatDate() {\n const tr = useTr();\n return (date?: string) => {\n if (!date) return tr('common.status.pending', 'Pending');\n return new Date(date).toLocaleDateString();\n };\n}\n\nexport function MembersListPage() {\n const tr = useTr();\n const formatDate = useFormatDate();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n // Follow the platform default-allow feature flag policy: unseeded flags must\n // not disable shipped invite actions, while explicit false still gates them.\n const inviteDetails = useFeatureFlagDetails('organization.invite');\n const inviteEnabled = inviteDetails ? Boolean(inviteDetails.value) : true;\n const { organizationId } = useParams<{ organizationId: string }>();\n const [searchQuery, setSearchQuery] = useState('');\n const debouncedSearchQuery = useDebouncedValue(searchQuery, 250);\n const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(new Set());\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n const { members, loading, error, refetch } = useMembers(organizationId ?? '', 50, 0);\n const { removeMember, loading: deleteLoading } = useRemoveMember();\n\n const filteredMembers = useMemo(() => {\n if (!debouncedSearchQuery) {\n return members;\n }\n\n const normalizedQuery = debouncedSearchQuery.toLowerCase();\n return members.filter(\n (member) =>\n member.userId.toLowerCase().includes(normalizedQuery) ||\n member.role.toLowerCase().includes(normalizedQuery)\n );\n }, [debouncedSearchQuery, members]);\n\n useEffect(() => {\n setSelectedMemberIds((previousSelection) => {\n const visibleIds = new Set(filteredMembers.map((member) => member.id));\n const nextSelection = new Set(\n [...previousSelection].filter((memberId) => visibleIds.has(memberId))\n );\n\n if (nextSelection.size === previousSelection.size) {\n return previousSelection;\n }\n\n return nextSelection;\n });\n }, [filteredMembers]);\n\n const selectedMembers = useMemo(\n () => filteredMembers.filter((member) => selectedMemberIds.has(member.id)),\n [filteredMembers, selectedMemberIds]\n );\n\n const allVisibleSelected =\n filteredMembers.length > 0 &&\n filteredMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelected =\n filteredMembers.some((member) => selectedMemberIds.has(member.id)) && !allVisibleSelected;\n\n const executeRemoveMembers = async (userIds: string[]) => {\n if (!organizationId) return;\n\n try {\n const results = await Promise.allSettled(\n userIds.map(async (userId) => {\n const result = await removeMember(organizationId, userId);\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to remove member');\n }\n })\n );\n\n const rejectedResult = results.find((result) => result.status === 'rejected');\n if (rejectedResult?.status === 'rejected') {\n throw rejectedResult.reason;\n }\n\n setSelectedMemberIds(new Set());\n await refetch();\n toast.success(\n userIds.length === 1\n ? tr('members.remove.successSingle', 'Member removed successfully')\n : `${userIds.length} ${tr('members.remove.successMany', 'members removed successfully')}`\n );\n } catch (removeError) {\n toast.error(\n removeError instanceof Error\n ? removeError.message\n : tr('members.remove.failed', 'Failed to remove member')\n );\n }\n };\n\n const handleRemove = (userId: string) => {\n openConfirmDialog({\n title: tr('members.remove.title', 'Remove member'),\n description: `${tr('members.remove.descriptionPrefix', 'Remove')} ${userId} ${tr('members.remove.descriptionSuffix', 'from this organization? Workspace access inherited from this membership will be impacted.')}`,\n confirmText: tr('members.remove.confirm', 'Remove'),\n variant: 'danger',\n onConfirm: () => {\n void executeRemoveMembers([userId]);\n },\n });\n };\n\n const handleBulkRemove = () => {\n if (selectedMembers.length === 0) return;\n\n openConfirmDialog({\n title: tr('members.remove.bulkTitle', 'Remove selected members'),\n description: `${tr('members.remove.bulkPrefix', 'Remove')} ${selectedMembers.length} ${tr(\n selectedMembers.length === 1\n ? 'members.remove.bulkSuffixSingle'\n : 'members.remove.bulkSuffixMany',\n selectedMembers.length === 1\n ? 'selected member from this organization?'\n : 'selected members from this organization?'\n )}`,\n confirmText: `${tr('members.remove.confirm', 'Remove')} ${selectedMembers.length}`,\n variant: 'danger',\n onConfirm: () => {\n void executeRemoveMembers(selectedMembers.map((member) => member.userId));\n },\n });\n };\n\n const toggleMemberSelection = (memberId: string, checked: boolean) => {\n setSelectedMemberIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n if (checked) {\n nextSelection.add(memberId);\n } else {\n nextSelection.delete(memberId);\n }\n return nextSelection;\n });\n };\n\n const toggleAllMembers = (checked: boolean) => {\n setSelectedMemberIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n filteredMembers.forEach((member) => {\n if (checked) {\n nextSelection.add(member.id);\n } else {\n nextSelection.delete(member.id);\n }\n });\n return nextSelection;\n });\n };\n\n if (!organizationId) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={tr('members.list.missingOrg', 'Organization ID is required to view members')}\n action={{\n label: tr('members.list.goToOrganizations', 'Go to Organizations'),\n onClick: () => navigate(basePath),\n }}\n />\n </div>\n );\n }\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.MEMBER_LIST)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.list.noPermission', \"You don't have permission to view members.\")}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState message={tr('members.list.loading', 'Loading members...')} />\n </div>\n );\n }\n\n if (error) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={error.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetch();\n },\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n <div className=\"mx-auto flex w-full max-w-7xl flex-col gap-6\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-2\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.list.title', 'Members')}\n </h1>\n <p className=\"max-w-2xl text-[var(--color-text-secondary)]\">\n {tr(\n 'members.list.subtitle',\n 'Manage organization members, inspect their roles, and invite more people into this organization.'\n )}\n </p>\n </div>\n\n {inviteEnabled && hasPermission(PERMISSIONS.MEMBER_INVITE) && (\n <button type=\"button\"\n onClick={() => navigate(`${basePath}/${organizationId}/members/invite`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Mail size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">{tr('members.invite.button', 'Invite Member')}</span>\n </button>\n )}\n </div>\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-4 shadow-[var(--shadow-elevation-1)]\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('members.list.searchPlaceholder', 'Search by member ID or role...')}\n label={tr('members.list.searchLabel', 'Search organization members')}\n inputId=\"organization-members-search\"\n className=\"flex-1\"\n />\n\n <button type=\"button\"\n onClick={() => {\n void refetch();\n }}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-4 py-2 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-95\"\n aria-label={tr('members.list.refresh', 'Refresh members')}\n title={tr('members.list.refresh', 'Refresh members')}\n >\n <RefreshCw size={18} aria-hidden=\"true\" />\n </button>\n </div>\n\n {filteredMembers.length > 0 ? (\n <div className=\"mt-4 flex flex-col gap-3 rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <label className=\"flex items-center gap-2 text-sm text-[var(--color-text-primary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllMembers}\n ariaLabel={tr('members.list.selectAll', 'Select all members')}\n />\n <span>{tr('common.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n {selectedMemberIds.size > 0 ? (\n <button type=\"button\"\n onClick={() => setSelectedMemberIds(new Set())}\n className=\"text-sm text-[var(--color-text-secondary)] transition-colors hover:text-[var(--color-text-primary)]\"\n >\n {tr('common.clearSelection', 'Clear selection')}\n </button>\n ) : null}\n </div>\n\n <div className=\"flex flex-col gap-2 sm:flex-row sm:items-center\">\n <span className=\"text-sm text-[var(--color-text-secondary)]\">\n {selectedMemberIds.size} {tr('common.selected', 'selected')}\n </span>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button type=\"button\"\n onClick={handleBulkRemove}\n disabled={selectedMembers.length === 0 || deleteLoading}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n <span>{tr('common.removeSelected', 'Remove Selected')}</span>\n {selectedMembers.length > 0 ? <span>({selectedMembers.length})</span> : null}\n </button>\n )}\n </div>\n </div>\n ) : null}\n </div>\n\n {filteredMembers.length === 0 ? (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-6 shadow-[var(--shadow-elevation-1)]\">\n <EmptyState\n icon={<Shield size={40} aria-hidden=\"true\" />}\n title={tr('members.list.emptyTitle', 'No members found')}\n description={tr(\n 'members.list.emptyDescription',\n 'Invite someone to this organization to grant them access and assign their role.'\n )}\n action={\n inviteEnabled && hasPermission(PERMISSIONS.MEMBER_INVITE)\n ? {\n label: tr('members.invite.button', 'Invite Member'),\n onClick: () => navigate(`${basePath}/${organizationId}/members/invite`),\n }\n : undefined\n }\n />\n </div>\n ) : (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {filteredMembers.map((member) => (\n <article\n key={member.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-4 shadow-[var(--shadow-elevation-1)]\"\n >\n <div className=\"flex items-start gap-3\">\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n onChange={(checked) => toggleMemberSelection(member.id, checked)}\n ariaLabel={`${tr('members.list.selectMember', 'Select member')} ${member.userId}`}\n />\n\n <div className=\"flex min-w-0 flex-1 gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Shield size={18} aria-hidden=\"true\" />\n </div>\n\n <div className=\"min-w-0 flex-1 space-y-3\">\n <div>\n <div className=\"flex flex-wrap items-start gap-2\">\n <h2 className=\"break-all text-base font-semibold text-[var(--color-text-primary)]\">\n {member.userId}\n </h2>\n <MemberRoleBadge role={member.role} />\n </div>\n <p className=\"mt-1 break-all font-mono text-xs text-[var(--color-text-muted)]\">\n {tr('members.list.memberId', 'Member ID')}: {member.id}\n </p>\n </div>\n\n <dl className=\"grid grid-cols-1 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-[var(--color-text-primary)]\">\n {member.invitedBy || tr('common.notAvailable', 'N/A')}\n </dd>\n </div>\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </dt>\n <dd className=\"mt-1 text-[var(--color-text-primary)]\">\n {formatDate(member.joinedAt)}\n </dd>\n </div>\n </dl>\n </div>\n </div>\n </div>\n\n <div className=\"mt-4 flex flex-wrap gap-2 border-t border-[var(--color-border-default)] pt-4\">\n <button type=\"button\"\n onClick={() =>\n navigate(`${basePath}/${organizationId}/members/${member.userId}`)\n }\n className=\"inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-border-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n >\n <Eye size={16} aria-hidden=\"true\" />\n {tr('common.view', 'View')}\n </button>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button type=\"button\"\n onClick={() => handleRemove(member.userId)}\n disabled={deleteLoading}\n className=\"inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-sm text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Trash2 size={16} aria-hidden=\"true\" />\n {tr('members.remove.confirm', 'Remove')}\n </button>\n )}\n </div>\n </article>\n ))}\n </div>\n\n <div className=\"hidden overflow-hidden rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)] md:block\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-[var(--color-bg-subtle)]\">\n <tr>\n <th className=\"w-12 px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllMembers}\n ariaLabel={tr('members.list.selectAll', 'Select all members')}\n />\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.user', 'User')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </th>\n <th className=\"px-6 py-3 text-right text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody className=\"divide-y divide-[var(--color-border-default)]\">\n {filteredMembers.map((member) => (\n <tr\n key={member.id}\n className=\"transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <td className=\"px-6 py-4 align-top\">\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n onChange={(checked) => toggleMemberSelection(member.id, checked)}\n ariaLabel={`${tr('members.list.selectMember', 'Select member')} ${member.userId}`}\n />\n </td>\n <td className=\"px-6 py-4\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex size-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Shield size={18} aria-hidden=\"true\" />\n </div>\n <div className=\"min-w-0\">\n <p className=\"break-all font-medium text-[var(--color-text-primary)]\">\n {member.userId}\n </p>\n <p className=\"break-all font-mono text-xs text-[var(--color-text-muted)]\">\n {tr('common.idLabel', 'ID')}: {member.id}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-6 py-4 align-top\">\n <MemberRoleBadge role={member.role} />\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {member.invitedBy || tr('common.notAvailable', 'N/A')}\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {formatDate(member.joinedAt)}\n </td>\n <td className=\"px-6 py-4 align-top\">\n <div className=\"flex items-center justify-end gap-2\">\n <button type=\"button\"\n onClick={() =>\n navigate(`${basePath}/${organizationId}/members/${member.userId}`)\n }\n className=\"rounded-lg p-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-bg-muted)] hover:text-[var(--color-text-primary)]\"\n aria-label={`${tr('members.list.viewMember', 'View member')} ${member.userId}`}\n title={`${tr('members.list.viewMember', 'View member')} ${member.userId}`}\n >\n <Eye size={18} aria-hidden=\"true\" />\n </button>\n {hasPermission(PERMISSIONS.MEMBER_REMOVE) && (\n <button type=\"button\"\n onClick={() => handleRemove(member.userId)}\n disabled={deleteLoading}\n className=\"rounded-lg p-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label={`${tr('members.list.removeMember', 'Remove member')} ${member.userId}`}\n title={`${tr('members.list.removeMember', 'Remove member')} ${member.userId}`}\n >\n <Trash2 size={18} aria-hidden=\"true\" />\n </button>\n )}\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n )}\n </div>\n\n {ConfirmDialog}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -166,6 +166,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
166
166
  ...w,
167
167
  tenantId: e.target.value
168
168
  }),
169
+ "aria-label": x("organizations.form.tenant", "Tenant"),
169
170
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
170
171
  placeholder: x("organizations.form.tenantPlaceholder", "Enter tenant ID"),
171
172
  disabled: b,
@@ -198,6 +199,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
198
199
  ...w,
199
200
  name: e.target.value
200
201
  }),
202
+ "aria-label": x("organizations.form.name", "Organization Name"),
201
203
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
202
204
  placeholder: x("organizations.form.namePlaceholder", "Enter organization name"),
203
205
  disabled: b,
@@ -226,6 +228,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
226
228
  id: "slug",
227
229
  type: "text",
228
230
  value: w.slug,
231
+ "aria-label": x("common.slug", "Slug"),
229
232
  onChange: (e) => {
230
233
  k(!0), N.current = null, P.current = null, T({
231
234
  ...w,
@@ -258,6 +261,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
258
261
  ...w,
259
262
  description: e.target.value
260
263
  }),
264
+ "aria-label": x("common.description", "Description"),
261
265
  rows: 4,
262
266
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
263
267
  placeholder: x("organizations.form.descriptionPlaceholder", "Enter organization description"),
@@ -276,6 +280,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
276
280
  ...w,
277
281
  logo: e.target.value
278
282
  }),
283
+ "aria-label": x("organizations.form.logoUrl", "Logo URL"),
279
284
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
280
285
  placeholder: "https://example.com/logo.png",
281
286
  disabled: b,
@@ -293,6 +298,7 @@ function h({ organization: h, tenantId: g, tenantName: _, onSubmit: v, onCancel:
293
298
  ...w,
294
299
  website: e.target.value
295
300
  }),
301
+ "aria-label": x("organizations.detail.website", "Website"),
296
302
  className: "\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n ",
297
303
  placeholder: "https://example.com",
298
304
  disabled: b,
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationForm.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n const [formData, setFormData] = useState({\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create) */}\n {isCreateMode && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OrganizationForm.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create) */}\n {isCreateMode && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n aria-label={tr('organizations.form.tenant', 'Tenant')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n aria-label={tr('organizations.form.name', 'Organization Name')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n aria-label={tr('common.description', 'Description')}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n aria-label={tr('organizations.form.logoUrl', 'Logo URL')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useOrgsWithPermission.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,IAAM,IAAgC;AAsBtC,SAAgB,EACd,GACA,GACA,GACiE;CACjE,IAAM,EAAE,YAAS,GAAS,EACpB,IAAU,GAAM,MAAM,IACtB,IAAS,GAAiB,EAM1B,IAAY,QAAc,CAAC,GAAG,EAAO,EAAE,CAD3B,QAAc,EAAO,KAAK,GAAG,EAAE,CAAC,EAAO,CAAC,CACF,CAAC,EAEnD,IAAS,QAEX,EAAU,KAAK,OAAa;EAC1B;EACA,WAAW,EAAU;EACrB;EACA;EACA;EACD,EAAE,EACL;EAAC;EAAW;EAAS;EAAc;EAAO,CAC3C,EAEK,IAAO,CAAC,KAAW,EAAU,WAAW,GAOxC,CAAC,GAAS,KAAc,EAAkC,EAAE,CAAC,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAU,KAAe,EAAS,GAAM;AA2D/C,QAzDA,QAAgB;AACd,MAAI,GAAM;AAGR,GAFA,EAAW,EAAE,CAAC,EACd,EAAW,GAAM,EACjB,EAAY,GAAM;AAClB;;EAGF,IAAI,IAAY;AAEhB,EADA,EAAW,GAAK,EAChB,EAAY,GAAM;EAElB,IAAM,IAA4B,EAAE;AACpC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK,EACtC,GAAO,KAAK,EAAO,MAAM,GAAG,IAAI,EAA8B,CAAC;AAyBjE,SAtBA,QAAQ,IACN,EAAO,KAAK,MACV,EAAO,MAAqE;GAC1E,OAAO;GACP,WAAW,EAAE,QAAQ,GAAO;GAC5B,aAAa;GACd,CAAC,CACH,CACF,CACE,MAAM,MAAc;AACf,SAEJ,EADe,EAAU,SAAS,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC,CACrD,EAClB,EAAW,GAAM;IACjB,CACD,YAAY;AACP,SACJ,EAAW,EAAE,CAAC,EACd,EAAY,GAAK,EACjB,EAAW,GAAM;IACjB,QAES;AACX,OAAY;;IAEb;EAAC;EAAQ;EAAQ;EAAK,CAAC,EAenB;EACL,YAdiB,QAAc;AAC/B,OAAI,EAAM,wBAAO,IAAI,KAAa;GAClC,IAAM,oBAAM,IAAI,KAAa,EAIvB,IAAU,KAAK,IAAI,EAAQ,QAAQ,EAAU,OAAO;AAC1D,QAAK,IAAI,IAAI,GAAG,IAAI,GAAS,KAAK,EAChC,CAAI,EAAQ,GAAG,WAAW,EAAU,MAAI,EAAI,IAAI,EAAU,GAAG;AAE/D,UAAO;KACN;GAAC;GAAS;GAAW;GAAK,CAAC;EAI5B,SAAS,CAAC,KAAQ;EAClB,SAAS,CAAC,KAAQ;EACnB"}
1
+ {"version":3,"file":"useOrgsWithPermission.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n // react-doctor-disable-next-line react-doctor/exhaustive-deps\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,IAAM,IAAgC;AAsBtC,SAAgB,EACd,GACA,GACA,GACiE;CACjE,IAAM,EAAE,YAAS,GAAS,EACpB,IAAU,GAAM,MAAM,IACtB,IAAS,GAAiB,EAO1B,IAAY,QAAc,CAAC,GAAG,EAAO,EAAE,CAF3B,QAAc,EAAO,KAAK,GAAG,EAAE,CAAC,EAAO,CAAC,CAEF,CAAC,EAEnD,IAAS,QAEX,EAAU,KAAK,OAAa;EAC1B;EACA,WAAW,EAAU;EACrB;EACA;EACA;EACD,EAAE,EACL;EAAC;EAAW;EAAS;EAAc;EAAO,CAC3C,EAEK,IAAO,CAAC,KAAW,EAAU,WAAW,GAOxC,CAAC,GAAS,KAAc,EAAkC,EAAE,CAAC,EAC7D,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAU,KAAe,EAAS,GAAM;AA2D/C,QAzDA,QAAgB;AACd,MAAI,GAAM;AAGR,GAFA,EAAW,EAAE,CAAC,EACd,EAAW,GAAM,EACjB,EAAY,GAAM;AAClB;;EAGF,IAAI,IAAY;AAEhB,EADA,EAAW,GAAK,EAChB,EAAY,GAAM;EAElB,IAAM,IAA4B,EAAE;AACpC,OAAK,IAAI,IAAI,GAAG,IAAI,EAAO,QAAQ,KAAK,EACtC,GAAO,KAAK,EAAO,MAAM,GAAG,IAAI,EAA8B,CAAC;AAyBjE,SAtBA,QAAQ,IACN,EAAO,KAAK,MACV,EAAO,MAAqE;GAC1E,OAAO;GACP,WAAW,EAAE,QAAQ,GAAO;GAC5B,aAAa;GACd,CAAC,CACH,CACF,CACE,MAAM,MAAc;AACf,SAEJ,EADe,EAAU,SAAS,MAAM,EAAE,MAAM,oBAAoB,EAAE,CAAC,CACrD,EAClB,EAAW,GAAM;IACjB,CACD,YAAY;AACP,SACJ,EAAW,EAAE,CAAC,EACd,EAAY,GAAK,EACjB,EAAW,GAAM;IACjB,QAES;AACX,OAAY;;IAEb;EAAC;EAAQ;EAAQ;EAAK,CAAC,EAenB;EACL,YAdiB,QAAc;AAC/B,OAAI,EAAM,wBAAO,IAAI,KAAa;GAClC,IAAM,oBAAM,IAAI,KAAa,EAIvB,IAAU,KAAK,IAAI,EAAQ,QAAQ,EAAU,OAAO;AAC1D,QAAK,IAAI,IAAI,GAAG,IAAI,GAAS,KAAK,EAChC,CAAI,EAAQ,GAAG,WAAW,EAAU,MAAI,EAAI,IAAI,EAAU,GAAG;AAE/D,UAAO;KACN;GAAC;GAAS;GAAW;GAAK,CAAC;EAI5B,SAAS,CAAC,KAAQ;EAClB,SAAS,CAAC,KAAQ;EACnB"}
@@ -33,6 +33,7 @@ function p() {
33
33
  className: "mb-6",
34
34
  children: [
35
35
  /* @__PURE__ */ f("button", {
36
+ type: "button",
36
37
  onClick: w,
37
38
  className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n ",
38
39
  children: [/* @__PURE__ */ d(l, { size: 20 }), /* @__PURE__ */ d("span", {
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n const selectedTenant = tenantId\n ? tenants.find((tenant) => tenant.id === tenantId)\n : tenants.length === 1\n ? tenants[0]\n : undefined;\n\n const handleSubmit = async (data: CreateOrganizationInput | UpdateOrganizationInput) => {\n try {\n const createData = data as CreateOrganizationInput;\n const result = await createOrganization(createData);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n\n if (result.data?.createOrganization) {\n toast.success(tr('organizations.create.success', 'Organization created successfully'));\n navigate(`${basePath}/${result.data.createOrganization.id}`);\n }\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(basePath);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_CREATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.create.noPermission',\n \"You don't have permission to create organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n {/* Page Header */}\n <div className=\"mb-6\">\n <button\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.create.title', 'Create Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.create.subtitle', 'Add a new organization to a tenant')}\n </p>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,IAAyB;AACvC,GAAwB,uBAAuB;CAC/C,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,kBAAkB,gBAAa,GAAe,EACrE,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,uBAAoB,eAAY,GAAuB,EACzD,EAAE,eAAY,EAAW,KAAK,GAAG,GAAK,EAEtC,IAAiB,IACnB,EAAQ,MAAM,MAAW,EAAO,OAAO,EAAS,GAChD,EAAQ,WAAW,IACjB,EAAQ,KACR,KAAA,GAEA,IAAe,OAAO,MAA4D;AACtF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EACgC;AACnD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,+BAA+B,gCAAgC,CACrE;AAGH,GAAI,EAAO,MAAM,uBACf,EAAM,QAAQ,EAAG,gCAAgC,oCAAoC,CAAC,EACtF,EAAS,GAAG,EAAS,GAAG,EAAO,KAAK,mBAAmB,KAAK;WAEvD,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,+BAA+B,gCAAgC,CACvE;;IAIC,UAAqB;AACzB,IAAS,EAAS;;AAwBpB,QArBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAcxC,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,SAAS;KACT,WAAU;eAFZ,CAUE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,6BAA6B,wBAAwB;MACpD,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,sBAAsB;KACrD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,iCAAiC,qCAAqC;KACxE,CAAA;IACA;MAGN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,KAAY,GAAgB;KACtC,YAAY,GAAgB;KAC5B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MAnDJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,qCACA,qDACD;GACG,CAAA;EACF,CAAA"}
1
+ {"version":3,"file":"OrganizationCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n const selectedTenant = tenantId\n ? tenants.find((tenant) => tenant.id === tenantId)\n : tenants.length === 1\n ? tenants[0]\n : undefined;\n\n const handleSubmit = async (data: CreateOrganizationInput | UpdateOrganizationInput) => {\n try {\n const createData = data as CreateOrganizationInput;\n const result = await createOrganization(createData);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n\n if (result.data?.createOrganization) {\n toast.success(tr('organizations.create.success', 'Organization created successfully'));\n navigate(`${basePath}/${result.data.createOrganization.id}`);\n }\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(basePath);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_CREATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.create.noPermission',\n \"You don't have permission to create organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n {/* Page Header */}\n <div className=\"mb-6\">\n <button type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.create.title', 'Create Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.create.subtitle', 'Add a new organization to a tenant')}\n </p>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,IAAyB;AACvC,GAAwB,uBAAuB;CAC/C,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,kBAAkB,gBAAa,GAAe,EACrE,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,uBAAoB,eAAY,GAAuB,EACzD,EAAE,eAAY,EAAW,KAAK,GAAG,GAAK,EAEtC,IAAiB,IACnB,EAAQ,MAAM,MAAW,EAAO,OAAO,EAAS,GAChD,EAAQ,WAAW,IACjB,EAAQ,KACR,KAAA,GAEA,IAAe,OAAO,MAA4D;AACtF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EACgC;AACnD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,+BAA+B,gCAAgC,CACrE;AAGH,GAAI,EAAO,MAAM,uBACf,EAAM,QAAQ,EAAG,gCAAgC,oCAAoC,CAAC,EACtF,EAAS,GAAG,EAAS,GAAG,EAAO,KAAK,mBAAmB,KAAK;WAEvD,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,+BAA+B,gCAAgC,CACvE;;IAIC,UAAqB;AACzB,IAAS,EAAS;;AAwBpB,QArBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAcxC,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KAAQ,MAAK;KACX,SAAS;KACT,WAAU;eAFZ,CAUE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,6BAA6B,wBAAwB;MACpD,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,sBAAsB;KACrD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,iCAAiC,qCAAqC;KACxE,CAAA;IACA;MAGN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,KAAY,GAAgB;KACtC,YAAY,GAAgB;KAC5B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MAnDJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,qCACA,qDACD;GACG,CAAA;EACF,CAAA"}
@@ -61,6 +61,7 @@ function C() {
61
61
  children: [/* @__PURE__ */ b("div", {
62
62
  className: "space-y-4",
63
63
  children: [/* @__PURE__ */ b("button", {
64
+ type: "button",
64
65
  onClick: () => T(E),
65
66
  className: "inline-flex min-h-10 items-center gap-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:text-[var(--color-text-primary)] active:scale-[0.98]",
66
67
  children: [/* @__PURE__ */ y(p, {
@@ -75,7 +76,7 @@ function C() {
75
76
  children: [/* @__PURE__ */ b("div", {
76
77
  className: "flex min-w-0 items-start gap-4",
77
78
  children: [/* @__PURE__ */ y("div", {
78
- className: "flex h-16 w-16 items-center justify-center rounded-xl bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]",
79
+ className: "flex size-16 items-center justify-center rounded-xl bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]",
79
80
  children: /* @__PURE__ */ y(h, {
80
81
  size: 28,
81
82
  "aria-hidden": "true"
@@ -103,6 +104,7 @@ function C() {
103
104
  }), /* @__PURE__ */ b("div", {
104
105
  className: "flex flex-col gap-3 sm:flex-row",
105
106
  children: [D(s.MEMBER_LIST) && /* @__PURE__ */ b("button", {
107
+ type: "button",
106
108
  onClick: () => T(`${E}/${w}/members`),
107
109
  className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] px-4 py-2.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]",
108
110
  children: [/* @__PURE__ */ y(_, {
@@ -110,6 +112,7 @@ function C() {
110
112
  "aria-hidden": "true"
111
113
  }), /* @__PURE__ */ y("span", { children: D(s.MEMBER_INVITE) ? C("organizations.detail.manageMembers", "Manage Members") : C("organizations.detail.viewMembers", "View Members") })]
112
114
  }), D(s.ORG_UPDATE) && /* @__PURE__ */ b("button", {
115
+ type: "button",
113
116
  onClick: () => T(`${E}/${w}/edit`),
114
117
  className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
115
118
  children: [/* @__PURE__ */ y(g, {
@@ -128,6 +131,7 @@ function C() {
128
131
  children: R.map((e) => {
129
132
  let t = M === e.id;
130
133
  return /* @__PURE__ */ y("button", {
134
+ type: "button",
131
135
  onClick: () => N(e.id),
132
136
  className: `inline-flex min-h-11 items-center rounded-t-lg border-b-2 px-4 py-3 text-sm font-medium transition-colors ${t ? "border-[var(--color-action-primary-bg)] text-[var(--color-action-primary-bg)]" : "border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]"}`,
133
137
  "aria-pressed": t,
@@ -246,6 +250,7 @@ function C() {
246
250
  className: "text-sm text-[var(--color-text-secondary)]",
247
251
  children: C("organizations.detail.workspacesSubtitle", "Create and manage workspaces for this organization.")
248
252
  })] }), D(s.WORKSPACE_CREATE) && /* @__PURE__ */ b("button", {
253
+ type: "button",
249
254
  onClick: () => T(`${E}/workspaces/new?organizationId=${w}`),
250
255
  className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
251
256
  children: [/* @__PURE__ */ y(m, {