@burdenoff/microfe-organization 2026.616.1 → 2026.624.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 (53) hide show
  1. package/dist/organization-management/AdminOrganizationRoot.js +4 -4
  2. package/dist/organization-management/AdminOrganizationRoot.js.map +1 -1
  3. package/dist/organization-management/UserOrganizationRoot.js +4 -4
  4. package/dist/organization-management/UserOrganizationRoot.js.map +1 -1
  5. package/dist/organization-management/modules/members/components/BulkInviteForm.js +157 -176
  6. package/dist/organization-management/modules/members/components/BulkInviteForm.js.map +1 -1
  7. package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -2
  8. package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
  9. package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -2
  10. package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
  11. package/dist/organization-management/modules/members/components/PendingInvitesList.js +78 -101
  12. package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
  13. package/dist/organization-management/modules/members/pages/InvitePeoplePage.js +1 -1
  14. package/dist/organization-management/modules/members/pages/InvitePeoplePage.js.map +1 -1
  15. package/dist/organization-management/modules/members/pages/MemberDetailPage.js +2 -2
  16. package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
  17. package/dist/organization-management/modules/members/pages/MembersListPage.js +3 -3
  18. package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
  19. package/dist/organization-management/modules/members/pages/MyInvitesPage.js +2 -2
  20. package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
  21. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +5 -5
  22. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
  23. package/dist/organization-management/modules/organizations/components/OrganizationForm.js +2 -2
  24. package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
  25. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +2 -2
  26. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
  27. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +4 -4
  28. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
  29. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +2 -2
  30. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
  31. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +5 -5
  32. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
  33. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +3 -3
  34. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
  35. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +2 -2
  36. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
  37. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +2 -2
  38. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
  39. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +215 -285
  40. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
  41. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +2 -2
  42. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
  43. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +4 -4
  44. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
  45. package/dist/organization-management/shared/components/feedback/EmptyState.js +2 -2
  46. package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
  47. package/dist/organization-management/shared/components/feedback/ErrorState.js +2 -2
  48. package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
  49. package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -1
  50. package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
  51. package/dist/organization-management/shared/components/input/SearchInput.js +1 -1
  52. package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
  53. package/package.json +1 -1
@@ -109,7 +109,7 @@ function D({ mode: D = "my" } = {}) {
109
109
  }
110
110
  })
111
111
  }) : /* @__PURE__ */ T("div", {
112
- className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300",
112
+ className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200",
113
113
  children: [/* @__PURE__ */ T("div", {
114
114
  className: "mx-auto flex w-full max-w-7xl flex-col gap-6",
115
115
  children: [
@@ -130,7 +130,7 @@ function D({ mode: D = "my" } = {}) {
130
130
  children: [D === "my" && /* @__PURE__ */ T("button", {
131
131
  type: "button",
132
132
  onClick: () => k(`${A}/invites`),
133
- className: "relative 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.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98]",
133
+ className: "relative 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.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]",
134
134
  "aria-label": O("organizations.invitations.button", "Invitations"),
135
135
  "data-tour": "organizations-invitations-button",
136
136
  children: [
@@ -143,7 +143,7 @@ function D({ mode: D = "my" } = {}) {
143
143
  children: O("organizations.invitations.button", "Invitations")
144
144
  }),
145
145
  V > 0 && /* @__PURE__ */ w("span", {
146
- className: "ml-1 inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-[var(--color-action-primary-bg)] px-1.5 py-0.5 text-xs font-semibold text-[var(--color-action-primary-text)]",
146
+ className: "ml-1 inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-[var(--color-action-primary-bg)] px-1.5 py-0.5 text-xs font-semibold tabular-nums text-[var(--color-action-primary-text)]",
147
147
  "aria-label": `${V} pending`,
148
148
  children: V
149
149
  })
@@ -151,7 +151,7 @@ function D({ mode: D = "my" } = {}) {
151
151
  }), N && j(d.ORG_CREATE) && /* @__PURE__ */ T("button", {
152
152
  type: "button",
153
153
  onClick: () => k(`${A}/new`),
154
- 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]",
154
+ 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",
155
155
  "data-tour": "organizations-create-button",
156
156
  children: [/* @__PURE__ */ w(le, {
157
157
  size: 18,
@@ -180,7 +180,7 @@ function D({ mode: D = "my" } = {}) {
180
180
  onClick: () => {
181
181
  W();
182
182
  },
183
- 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",
183
+ 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)]",
184
184
  "aria-label": O("organizations.list.refresh", "Refresh organizations"),
185
185
  title: O("organizations.list.refresh", "Refresh organizations"),
186
186
  "data-tour": "organizations-refresh-button",
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationsListPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationsListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building2, Eye, Mail, Plus, RefreshCw, SquarePen, Trash2 } from 'lucide-react';\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 {\n useDeleteOrganization,\n useMyOrganizations,\n useOrganizations,\n} from '../hooks/useOrganizations';\nimport { useMyInvites } from '../../members/hooks/useInvites';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { OrganizationStatusBadge } from '../components/OrganizationStatusBadge';\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';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport {\n isNative,\n nativeConfirm,\n nativeImpact,\n nativeNotify,\n nativeToast,\n} from '../../../../utils/nativeBridge';\n\nfunction formatDate(date: string) {\n return new Date(date).toLocaleDateString();\n}\n\n/**\n * `mode` controls which scope of organizations the page renders:\n *\n * - `'my'` (default) calls `myOrganizations` (gateway @rbac(scopeType:\"self\")).\n * Use this in product-app shells (e.g. vibecontrols) where the user should\n * see only orgs they are a member of. Membership IS the access list, so no\n * client-side permission gate is needed.\n *\n * - `'admin'` calls `allOrganizations` (gateway @rbac(scopeType: \"global\")).\n * Restricted to actors with global `organization:read` (super admins /\n * service / custom global-admin roles). Use this in the admin app where\n * operators need the tenant-wide list.\n */\nexport interface OrganizationsListPageProps {\n mode?: 'my' | 'admin';\n}\n\nexport function OrganizationsListPage({ mode = 'my' }: OrganizationsListPageProps = {}) {\n useOrganizationPageView('organizations.list');\n const tr = useTr();\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 CRUDL, while an explicit false value still gates create.\n const createDetails = useFeatureFlagDetails('organization.create');\n const createEnabled = createDetails ? Boolean(createDetails.value) : true;\n const [searchQuery, setSearchQuery] = useState('');\n const debouncedSearchQuery = useDebouncedValue(searchQuery, 300);\n const [selectedOrganizationIds, setSelectedOrganizationIds] = useState<Set<string>>(new Set());\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n // Show a count badge of pending invites alongside the Invitations link.\n // Skipped in 'admin' mode since admins land here via tenant-wide management\n // rather than personal invites.\n const { total: pendingInvitesCount } = useMyInvites(InviteStatus.Pending, 1, 0, mode === 'my');\n\n // Page size is large enough to cover realistic memberships / tenant-wide\n // org counts in one round-trip so the client-side search is exhaustive.\n // If a tenant ever exceeds this, wire in fetchMore/pagination instead of\n // silently truncating.\n const ORGANIZATIONS_PAGE_SIZE = 200;\n const myResult = useMyOrganizations(ORGANIZATIONS_PAGE_SIZE, 0, mode === 'my');\n const adminResult = useOrganizations(undefined, ORGANIZATIONS_PAGE_SIZE, 0, mode === 'admin');\n const {\n organizations: allOrganizations,\n loading,\n error,\n refetch,\n } = mode === 'admin' ? adminResult : myResult;\n\n const isSearchFiltered = debouncedSearchQuery.trim().length > 0;\n\n const organizations = useMemo(() => {\n const q = debouncedSearchQuery.trim().toLowerCase();\n if (!q) return allOrganizations;\n return allOrganizations.filter(\n (org) => org.name?.toLowerCase().includes(q) || org.slug?.toLowerCase().includes(q)\n );\n }, [allOrganizations, debouncedSearchQuery]);\n\n const { deleteOrganization, loading: deleteLoading } = useDeleteOrganization();\n\n useEffect(() => {\n setSelectedOrganizationIds((previousSelection) => {\n const visibleIds = new Set(organizations.map((organization) => organization.id));\n const nextSelection = new Set(\n [...previousSelection].filter((organizationId) => visibleIds.has(organizationId))\n );\n\n if (nextSelection.size === previousSelection.size) {\n return previousSelection;\n }\n\n return nextSelection;\n });\n }, [organizations]);\n\n const selectedOrganizations = useMemo(\n () => organizations.filter((organization) => selectedOrganizationIds.has(organization.id)),\n [organizations, selectedOrganizationIds]\n );\n\n const allVisibleSelected =\n organizations.length > 0 &&\n organizations.every((organization) => selectedOrganizationIds.has(organization.id));\n const partiallySelected =\n organizations.some((organization) => selectedOrganizationIds.has(organization.id)) &&\n !allVisibleSelected;\n\n const executeDelete = async (organizationIds: string[]) => {\n try {\n const results = await Promise.allSettled(\n organizationIds.map(async (organizationId) => {\n const result = await deleteOrganization(organizationId);\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to delete organization');\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 setSelectedOrganizationIds(new Set());\n await refetch();\n const successMsg =\n organizationIds.length === 1\n ? tr('organizations.delete.successSingle', 'Organization deleted successfully')\n : `${organizationIds.length} ${tr('organizations.delete.successMany', 'organizations deleted successfully')}`;\n toast.success(successMsg);\n void nativeNotify('success');\n if (isNative()) void nativeToast(successMsg);\n } catch (deleteError) {\n const errorMsg =\n deleteError instanceof Error\n ? deleteError.message\n : tr('organizations.delete.failed', 'Failed to delete organization');\n toast.error(errorMsg);\n void nativeNotify('error');\n if (isNative()) void nativeToast(errorMsg);\n }\n };\n\n const handleDelete = (organizationId: string, organizationName: string) => {\n nativeImpact('medium');\n const title = tr('organizations.delete.title', 'Delete organization');\n const description = `${tr('organizations.delete.confirmPrefix', 'Delete')} ${organizationName}? ${tr('common.cannotBeUndone', 'This action cannot be undone.')}`;\n void nativeConfirm({\n title,\n message: description,\n okButtonTitle: tr('common.delete', 'Delete'),\n }).then((nativeResult) => {\n if (nativeResult === null) {\n openConfirmDialog({\n title,\n description,\n confirmText: tr('common.delete', 'Delete'),\n variant: 'danger',\n onConfirm: () => {\n void executeDelete([organizationId]);\n },\n });\n } else if (nativeResult) {\n void executeDelete([organizationId]);\n }\n });\n };\n\n const handleBulkDelete = () => {\n if (selectedOrganizations.length === 0) return;\n\n openConfirmDialog({\n title: tr('organizations.delete.bulkTitle', 'Delete selected organizations'),\n description: `${tr('organizations.delete.bulkPrefix', 'Delete')} ${selectedOrganizations.length} ${tr(\n selectedOrganizations.length === 1\n ? 'organizations.delete.bulkSuffixSingle'\n : 'organizations.delete.bulkSuffixMany',\n selectedOrganizations.length === 1 ? 'selected organization?' : 'selected organizations?'\n )} ${tr('common.cannotBeUndone', 'This action cannot be undone.')}`,\n confirmText: `${tr('common.delete', 'Delete')} ${selectedOrganizations.length}`,\n variant: 'danger',\n onConfirm: () => {\n void executeDelete(selectedOrganizations.map((organization) => organization.id));\n },\n });\n };\n\n const toggleOrganizationSelection = (organizationId: string, checked: boolean) => {\n setSelectedOrganizationIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n if (checked) {\n nextSelection.add(organizationId);\n } else {\n nextSelection.delete(organizationId);\n }\n return nextSelection;\n });\n };\n\n const toggleAllOrganizations = (checked: boolean) => {\n setSelectedOrganizationIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n organizations.forEach((organization) => {\n if (checked) {\n nextSelection.add(organization.id);\n } else {\n nextSelection.delete(organization.id);\n }\n });\n return nextSelection;\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 // No client-side list gate: this page calls `myOrganizations`\n // (@rbac(scopeType: \"self\")), so the data set IS the access list — if the\n // user is in zero orgs the page renders the empty state, not a deny. The\n // create/delete/edit buttons keep their own per-action `hasPermission`\n // checks below to hide actions the user can't perform on a given org.\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('organizations.list.loading', 'Loading organizations...')} />\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\" data-tour=\"organizations-page-header\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {tr('organizations.list.title', 'Organizations')}\n </h1>\n <p className=\"max-w-2xl text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.list.subtitle',\n 'Manage tenant organizations and the workspaces that belong to them.'\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {mode === 'my' && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/invites`)}\n className=\"relative 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.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98]\"\n aria-label={tr('organizations.invitations.button', 'Invitations')}\n data-tour=\"organizations-invitations-button\"\n >\n <Mail size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr('organizations.invitations.button', 'Invitations')}\n </span>\n {pendingInvitesCount > 0 && (\n <span\n className=\"ml-1 inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-[var(--color-action-primary-bg)] px-1.5 py-0.5 text-xs font-semibold text-[var(--color-action-primary-text)]\"\n aria-label={`${pendingInvitesCount} pending`}\n >\n {pendingInvitesCount}\n </span>\n )}\n </button>\n )}\n\n {createEnabled && hasPermission(PERMISSIONS.ORG_CREATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/new`)}\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 data-tour=\"organizations-create-button\"\n >\n <Plus size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr('organizations.create.button', 'Create Organization')}\n </span>\n </button>\n )}\n </div>\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\n className=\"flex flex-col gap-3 sm:flex-row sm:items-center\"\n data-tour=\"organizations-search-input\"\n >\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('organizations.list.searchPlaceholder', 'Search organizations...')}\n label={tr('organizations.list.searchLabel', 'Search organizations')}\n inputId=\"organizations-search\"\n className=\"flex-1\"\n />\n\n <button\n 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('organizations.list.refresh', 'Refresh organizations')}\n title={tr('organizations.list.refresh', 'Refresh organizations')}\n data-tour=\"organizations-refresh-button\"\n >\n <RefreshCw size={18} aria-hidden=\"true\" />\n </button>\n </div>\n\n {organizations.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={toggleAllOrganizations}\n ariaLabel={tr('organizations.list.selectAll', 'Select all organizations')}\n />\n <span>{tr('common.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n {selectedOrganizationIds.size > 0 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedOrganizationIds(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 {selectedOrganizationIds.size} {tr('common.selected', 'selected')}\n </span>\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={handleBulkDelete}\n disabled={selectedOrganizations.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.deleteSelected', 'Delete Selected')}</span>\n {selectedOrganizations.length > 0 ? (\n <span>({selectedOrganizations.length})</span>\n ) : null}\n </button>\n )}\n </div>\n </div>\n ) : null}\n </div>\n\n {organizations.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 {isSearchFiltered && allOrganizations.length > 0 ? (\n <EmptyState\n icon={<Building2 size={40} aria-hidden=\"true\" />}\n title={tr('organizations.list.noMatchesTitle', 'No matching organizations')}\n description={tr(\n 'organizations.list.noMatchesDescription',\n 'No organizations match your search. Try a different name or slug, or clear the search.'\n )}\n action={{\n label: tr('common.clearSearch', 'Clear search'),\n onClick: () => setSearchQuery(''),\n }}\n />\n ) : (\n <EmptyState\n icon={<Building2 size={40} aria-hidden=\"true\" />}\n title={tr('organizations.list.emptyTitle', 'No organizations found')}\n description={tr(\n 'organizations.list.emptyDescription',\n 'Create your first organization to start managing workspaces, members, and invites.'\n )}\n action={\n createEnabled && hasPermission(PERMISSIONS.ORG_CREATE)\n ? {\n label: tr('organizations.create.button', 'Create Organization'),\n onClick: () => navigate(`${basePath}/new`),\n }\n : undefined\n }\n />\n )}\n </div>\n ) : (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {organizations.map((organization) => (\n <article\n key={organization.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={selectedOrganizationIds.has(organization.id)}\n onChange={(checked) => toggleOrganizationSelection(organization.id, checked)}\n ariaLabel={`${tr('organizations.list.selectOne', 'Select organization')} ${organization.name}`}\n />\n\n <div className=\"flex min-w-0 flex-1 gap-3\">\n {organization.logo ? (\n <img\n src={organization.logo}\n alt={`${organization.name} ${tr('common.logo', 'logo')}`}\n className=\"size-12 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-12 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 text-base font-semibold text-[var(--color-action-primary-bg)]\">\n {organization.name[0]}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-start gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {organization.name}\n </h2>\n <OrganizationStatusBadge status={organization.status} />\n </div>\n <p className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-muted)]\">\n /{organization.slug}\n </p>\n <dl className=\"mt-3 grid grid-cols-2 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.list.column.workspaces', 'Workspaces')}\n </dt>\n <dd className=\"font-medium text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </dd>\n </div>\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('common.created', 'Created')}\n </dt>\n <dd className=\"font-medium text-[var(--color-text-primary)]\">\n {formatDate(organization.createdAt)}\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 type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}`)}\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.ORG_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}/edit`)}\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 <SquarePen size={16} aria-hidden=\"true\" />\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={() => handleDelete(organization.id, organization.name)}\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('common.delete', 'Delete')}\n </button>\n )}\n </div>\n </article>\n ))}\n </div>\n\n <div\n 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 data-tour=\"organizations-table\"\n >\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={toggleAllOrganizations}\n ariaLabel={tr('organizations.list.selectAll', 'Select all organizations')}\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('organizations.list.column.organization', 'Organization')}\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('common.status', 'Status')}\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('organizations.list.column.workspaces', 'Workspaces')}\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('common.created', 'Created')}\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 {organizations.map((organization) => (\n <tr\n key={organization.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={selectedOrganizationIds.has(organization.id)}\n onChange={(checked) =>\n toggleOrganizationSelection(organization.id, checked)\n }\n ariaLabel={`${tr('organizations.list.selectOne', 'Select organization')} ${organization.name}`}\n />\n </td>\n <td className=\"px-6 py-4\">\n <div className=\"flex items-start gap-3\">\n {organization.logo ? (\n <img\n src={organization.logo}\n alt={`${organization.name} ${tr('common.logo', 'logo')}`}\n className=\"size-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 font-semibold text-[var(--color-action-primary-bg)]\">\n {organization.name[0]}\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-[var(--color-text-primary)]\">\n {organization.name}\n </p>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-muted)]\">\n /{organization.slug}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-6 py-4 align-top\">\n <OrganizationStatusBadge status={organization.status} />\n </td>\n <td className=\"px-6 py-4 align-top text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {formatDate(organization.createdAt)}\n </td>\n <td className=\"px-6 py-4 align-top\">\n <div\n className=\"flex items-center justify-end gap-2\"\n {...(organization === organizations[0]\n ? { 'data-tour': 'organizations-table-actions' }\n : {})}\n >\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}`)}\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 title={`${tr('common.view', 'View')} ${organization.name}`}\n aria-label={`${tr('common.view', 'View')} ${organization.name}`}\n >\n <Eye size={18} aria-hidden=\"true\" />\n </button>\n {hasPermission(PERMISSIONS.ORG_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}/edit`)}\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 title={`${tr('common.edit', 'Edit')} ${organization.name}`}\n aria-label={`${tr('common.edit', 'Edit')} ${organization.name}`}\n >\n <SquarePen size={18} aria-hidden=\"true\" />\n </button>\n )}\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={() => handleDelete(organization.id, organization.name)}\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 title={`${tr('common.delete', 'Delete')} ${organization.name}`}\n aria-label={`${tr('common.delete', 'Delete')} ${organization.name}`}\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":"OrganizationsListPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationsListPage.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building2, Eye, Mail, Plus, RefreshCw, SquarePen, Trash2 } from 'lucide-react';\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 {\n useDeleteOrganization,\n useMyOrganizations,\n useOrganizations,\n} from '../hooks/useOrganizations';\nimport { useMyInvites } from '../../members/hooks/useInvites';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { OrganizationStatusBadge } from '../components/OrganizationStatusBadge';\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';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport {\n isNative,\n nativeConfirm,\n nativeImpact,\n nativeNotify,\n nativeToast,\n} from '../../../../utils/nativeBridge';\n\nfunction formatDate(date: string) {\n return new Date(date).toLocaleDateString();\n}\n\n/**\n * `mode` controls which scope of organizations the page renders:\n *\n * - `'my'` (default) calls `myOrganizations` (gateway @rbac(scopeType:\"self\")).\n * Use this in product-app shells (e.g. vibecontrols) where the user should\n * see only orgs they are a member of. Membership IS the access list, so no\n * client-side permission gate is needed.\n *\n * - `'admin'` calls `allOrganizations` (gateway @rbac(scopeType: \"global\")).\n * Restricted to actors with global `organization:read` (super admins /\n * service / custom global-admin roles). Use this in the admin app where\n * operators need the tenant-wide list.\n */\nexport interface OrganizationsListPageProps {\n mode?: 'my' | 'admin';\n}\n\nexport function OrganizationsListPage({ mode = 'my' }: OrganizationsListPageProps = {}) {\n useOrganizationPageView('organizations.list');\n const tr = useTr();\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 CRUDL, while an explicit false value still gates create.\n const createDetails = useFeatureFlagDetails('organization.create');\n const createEnabled = createDetails ? Boolean(createDetails.value) : true;\n const [searchQuery, setSearchQuery] = useState('');\n const debouncedSearchQuery = useDebouncedValue(searchQuery, 300);\n const [selectedOrganizationIds, setSelectedOrganizationIds] = useState<Set<string>>(new Set());\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n // Show a count badge of pending invites alongside the Invitations link.\n // Skipped in 'admin' mode since admins land here via tenant-wide management\n // rather than personal invites.\n const { total: pendingInvitesCount } = useMyInvites(InviteStatus.Pending, 1, 0, mode === 'my');\n\n // Page size is large enough to cover realistic memberships / tenant-wide\n // org counts in one round-trip so the client-side search is exhaustive.\n // If a tenant ever exceeds this, wire in fetchMore/pagination instead of\n // silently truncating.\n const ORGANIZATIONS_PAGE_SIZE = 200;\n const myResult = useMyOrganizations(ORGANIZATIONS_PAGE_SIZE, 0, mode === 'my');\n const adminResult = useOrganizations(undefined, ORGANIZATIONS_PAGE_SIZE, 0, mode === 'admin');\n const {\n organizations: allOrganizations,\n loading,\n error,\n refetch,\n } = mode === 'admin' ? adminResult : myResult;\n\n const isSearchFiltered = debouncedSearchQuery.trim().length > 0;\n\n const organizations = useMemo(() => {\n const q = debouncedSearchQuery.trim().toLowerCase();\n if (!q) return allOrganizations;\n return allOrganizations.filter(\n (org) => org.name?.toLowerCase().includes(q) || org.slug?.toLowerCase().includes(q)\n );\n }, [allOrganizations, debouncedSearchQuery]);\n\n const { deleteOrganization, loading: deleteLoading } = useDeleteOrganization();\n\n useEffect(() => {\n setSelectedOrganizationIds((previousSelection) => {\n const visibleIds = new Set(organizations.map((organization) => organization.id));\n const nextSelection = new Set(\n [...previousSelection].filter((organizationId) => visibleIds.has(organizationId))\n );\n\n if (nextSelection.size === previousSelection.size) {\n return previousSelection;\n }\n\n return nextSelection;\n });\n }, [organizations]);\n\n const selectedOrganizations = useMemo(\n () => organizations.filter((organization) => selectedOrganizationIds.has(organization.id)),\n [organizations, selectedOrganizationIds]\n );\n\n const allVisibleSelected =\n organizations.length > 0 &&\n organizations.every((organization) => selectedOrganizationIds.has(organization.id));\n const partiallySelected =\n organizations.some((organization) => selectedOrganizationIds.has(organization.id)) &&\n !allVisibleSelected;\n\n const executeDelete = async (organizationIds: string[]) => {\n try {\n const results = await Promise.allSettled(\n organizationIds.map(async (organizationId) => {\n const result = await deleteOrganization(organizationId);\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Failed to delete organization');\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 setSelectedOrganizationIds(new Set());\n await refetch();\n const successMsg =\n organizationIds.length === 1\n ? tr('organizations.delete.successSingle', 'Organization deleted successfully')\n : `${organizationIds.length} ${tr('organizations.delete.successMany', 'organizations deleted successfully')}`;\n toast.success(successMsg);\n void nativeNotify('success');\n if (isNative()) void nativeToast(successMsg);\n } catch (deleteError) {\n const errorMsg =\n deleteError instanceof Error\n ? deleteError.message\n : tr('organizations.delete.failed', 'Failed to delete organization');\n toast.error(errorMsg);\n void nativeNotify('error');\n if (isNative()) void nativeToast(errorMsg);\n }\n };\n\n const handleDelete = (organizationId: string, organizationName: string) => {\n nativeImpact('medium');\n const title = tr('organizations.delete.title', 'Delete organization');\n const description = `${tr('organizations.delete.confirmPrefix', 'Delete')} ${organizationName}? ${tr('common.cannotBeUndone', 'This action cannot be undone.')}`;\n void nativeConfirm({\n title,\n message: description,\n okButtonTitle: tr('common.delete', 'Delete'),\n }).then((nativeResult) => {\n if (nativeResult === null) {\n openConfirmDialog({\n title,\n description,\n confirmText: tr('common.delete', 'Delete'),\n variant: 'danger',\n onConfirm: () => {\n void executeDelete([organizationId]);\n },\n });\n } else if (nativeResult) {\n void executeDelete([organizationId]);\n }\n });\n };\n\n const handleBulkDelete = () => {\n if (selectedOrganizations.length === 0) return;\n\n openConfirmDialog({\n title: tr('organizations.delete.bulkTitle', 'Delete selected organizations'),\n description: `${tr('organizations.delete.bulkPrefix', 'Delete')} ${selectedOrganizations.length} ${tr(\n selectedOrganizations.length === 1\n ? 'organizations.delete.bulkSuffixSingle'\n : 'organizations.delete.bulkSuffixMany',\n selectedOrganizations.length === 1 ? 'selected organization?' : 'selected organizations?'\n )} ${tr('common.cannotBeUndone', 'This action cannot be undone.')}`,\n confirmText: `${tr('common.delete', 'Delete')} ${selectedOrganizations.length}`,\n variant: 'danger',\n onConfirm: () => {\n void executeDelete(selectedOrganizations.map((organization) => organization.id));\n },\n });\n };\n\n const toggleOrganizationSelection = (organizationId: string, checked: boolean) => {\n setSelectedOrganizationIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n if (checked) {\n nextSelection.add(organizationId);\n } else {\n nextSelection.delete(organizationId);\n }\n return nextSelection;\n });\n };\n\n const toggleAllOrganizations = (checked: boolean) => {\n setSelectedOrganizationIds((previousSelection) => {\n const nextSelection = new Set(previousSelection);\n organizations.forEach((organization) => {\n if (checked) {\n nextSelection.add(organization.id);\n } else {\n nextSelection.delete(organization.id);\n }\n });\n return nextSelection;\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 // No client-side list gate: this page calls `myOrganizations`\n // (@rbac(scopeType: \"self\")), so the data set IS the access list — if the\n // user is in zero orgs the page renders the empty state, not a deny. The\n // create/delete/edit buttons keep their own per-action `hasPermission`\n // checks below to hide actions the user can't perform on a given org.\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('organizations.list.loading', 'Loading organizations...')} />\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-200\">\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\" data-tour=\"organizations-page-header\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {tr('organizations.list.title', 'Organizations')}\n </h1>\n <p className=\"max-w-2xl text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.list.subtitle',\n 'Manage tenant organizations and the workspaces that belong to them.'\n )}\n </p>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n {mode === 'my' && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/invites`)}\n className=\"relative 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.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n aria-label={tr('organizations.invitations.button', 'Invitations')}\n data-tour=\"organizations-invitations-button\"\n >\n <Mail size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr('organizations.invitations.button', 'Invitations')}\n </span>\n {pendingInvitesCount > 0 && (\n <span\n className=\"ml-1 inline-flex min-w-[1.25rem] items-center justify-center rounded-full bg-[var(--color-action-primary-bg)] px-1.5 py-0.5 text-xs font-semibold tabular-nums text-[var(--color-action-primary-text)]\"\n aria-label={`${pendingInvitesCount} pending`}\n >\n {pendingInvitesCount}\n </span>\n )}\n </button>\n )}\n\n {createEnabled && hasPermission(PERMISSIONS.ORG_CREATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/new`)}\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\"\n data-tour=\"organizations-create-button\"\n >\n <Plus size={18} aria-hidden=\"true\" />\n <span className=\"font-medium\">\n {tr('organizations.create.button', 'Create Organization')}\n </span>\n </button>\n )}\n </div>\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\n className=\"flex flex-col gap-3 sm:flex-row sm:items-center\"\n data-tour=\"organizations-search-input\"\n >\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('organizations.list.searchPlaceholder', 'Search organizations...')}\n label={tr('organizations.list.searchLabel', 'Search organizations')}\n inputId=\"organizations-search\"\n className=\"flex-1\"\n />\n\n <button\n 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)]\"\n aria-label={tr('organizations.list.refresh', 'Refresh organizations')}\n title={tr('organizations.list.refresh', 'Refresh organizations')}\n data-tour=\"organizations-refresh-button\"\n >\n <RefreshCw size={18} aria-hidden=\"true\" />\n </button>\n </div>\n\n {organizations.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={toggleAllOrganizations}\n ariaLabel={tr('organizations.list.selectAll', 'Select all organizations')}\n />\n <span>{tr('common.selectAllVisible', 'Select all visible')}</span>\n </label>\n\n {selectedOrganizationIds.size > 0 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedOrganizationIds(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 {selectedOrganizationIds.size} {tr('common.selected', 'selected')}\n </span>\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={handleBulkDelete}\n disabled={selectedOrganizations.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.deleteSelected', 'Delete Selected')}</span>\n {selectedOrganizations.length > 0 ? (\n <span>({selectedOrganizations.length})</span>\n ) : null}\n </button>\n )}\n </div>\n </div>\n ) : null}\n </div>\n\n {organizations.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 {isSearchFiltered && allOrganizations.length > 0 ? (\n <EmptyState\n icon={<Building2 size={40} aria-hidden=\"true\" />}\n title={tr('organizations.list.noMatchesTitle', 'No matching organizations')}\n description={tr(\n 'organizations.list.noMatchesDescription',\n 'No organizations match your search. Try a different name or slug, or clear the search.'\n )}\n action={{\n label: tr('common.clearSearch', 'Clear search'),\n onClick: () => setSearchQuery(''),\n }}\n />\n ) : (\n <EmptyState\n icon={<Building2 size={40} aria-hidden=\"true\" />}\n title={tr('organizations.list.emptyTitle', 'No organizations found')}\n description={tr(\n 'organizations.list.emptyDescription',\n 'Create your first organization to start managing workspaces, members, and invites.'\n )}\n action={\n createEnabled && hasPermission(PERMISSIONS.ORG_CREATE)\n ? {\n label: tr('organizations.create.button', 'Create Organization'),\n onClick: () => navigate(`${basePath}/new`),\n }\n : undefined\n }\n />\n )}\n </div>\n ) : (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {organizations.map((organization) => (\n <article\n key={organization.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={selectedOrganizationIds.has(organization.id)}\n onChange={(checked) => toggleOrganizationSelection(organization.id, checked)}\n ariaLabel={`${tr('organizations.list.selectOne', 'Select organization')} ${organization.name}`}\n />\n\n <div className=\"flex min-w-0 flex-1 gap-3\">\n {organization.logo ? (\n <img\n src={organization.logo}\n alt={`${organization.name} ${tr('common.logo', 'logo')}`}\n className=\"size-12 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-12 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 text-base font-semibold text-[var(--color-action-primary-bg)]\">\n {organization.name[0]}\n </div>\n )}\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex flex-wrap items-start gap-2\">\n <h2 className=\"text-base font-semibold text-[var(--color-text-primary)]\">\n {organization.name}\n </h2>\n <OrganizationStatusBadge status={organization.status} />\n </div>\n <p className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-muted)]\">\n /{organization.slug}\n </p>\n <dl className=\"mt-3 grid grid-cols-2 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.list.column.workspaces', 'Workspaces')}\n </dt>\n <dd className=\"font-medium text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </dd>\n </div>\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('common.created', 'Created')}\n </dt>\n <dd className=\"font-medium text-[var(--color-text-primary)]\">\n {formatDate(organization.createdAt)}\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 type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}`)}\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.ORG_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}/edit`)}\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 <SquarePen size={16} aria-hidden=\"true\" />\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={() => handleDelete(organization.id, organization.name)}\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('common.delete', 'Delete')}\n </button>\n )}\n </div>\n </article>\n ))}\n </div>\n\n <div\n 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 data-tour=\"organizations-table\"\n >\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={toggleAllOrganizations}\n ariaLabel={tr('organizations.list.selectAll', 'Select all organizations')}\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('organizations.list.column.organization', 'Organization')}\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('common.status', 'Status')}\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('organizations.list.column.workspaces', 'Workspaces')}\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('common.created', 'Created')}\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 {organizations.map((organization) => (\n <tr\n key={organization.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={selectedOrganizationIds.has(organization.id)}\n onChange={(checked) =>\n toggleOrganizationSelection(organization.id, checked)\n }\n ariaLabel={`${tr('organizations.list.selectOne', 'Select organization')} ${organization.name}`}\n />\n </td>\n <td className=\"px-6 py-4\">\n <div className=\"flex items-start gap-3\">\n {organization.logo ? (\n <img\n src={organization.logo}\n alt={`${organization.name} ${tr('common.logo', 'logo')}`}\n className=\"size-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-[var(--color-action-primary-bg)]/12 font-semibold text-[var(--color-action-primary-bg)]\">\n {organization.name[0]}\n </div>\n )}\n <div className=\"min-w-0\">\n <p className=\"font-medium text-[var(--color-text-primary)]\">\n {organization.name}\n </p>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-muted)]\">\n /{organization.slug}\n </p>\n </div>\n </div>\n </td>\n <td className=\"px-6 py-4 align-top\">\n <OrganizationStatusBadge status={organization.status} />\n </td>\n <td className=\"px-6 py-4 align-top text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </td>\n <td className=\"px-6 py-4 align-top text-sm text-[var(--color-text-secondary)]\">\n {formatDate(organization.createdAt)}\n </td>\n <td className=\"px-6 py-4 align-top\">\n <div\n className=\"flex items-center justify-end gap-2\"\n {...(organization === organizations[0]\n ? { 'data-tour': 'organizations-table-actions' }\n : {})}\n >\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}`)}\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 title={`${tr('common.view', 'View')} ${organization.name}`}\n aria-label={`${tr('common.view', 'View')} ${organization.name}`}\n >\n <Eye size={18} aria-hidden=\"true\" />\n </button>\n {hasPermission(PERMISSIONS.ORG_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${organization.id}/edit`)}\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 title={`${tr('common.edit', 'Edit')} ${organization.name}`}\n aria-label={`${tr('common.edit', 'Edit')} ${organization.name}`}\n >\n <SquarePen size={18} aria-hidden=\"true\" />\n </button>\n )}\n {mode === 'admin' && hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button\n type=\"button\"\n onClick={() => handleDelete(organization.id, organization.name)}\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 title={`${tr('common.delete', 'Delete')} ${organization.name}`}\n aria-label={`${tr('common.delete', 'Delete')} ${organization.name}`}\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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"}
@@ -49,7 +49,7 @@ function o({ workspace: o, onClick: s, onEdit: c, onDelete: l, showActions: u =
49
49
  }), u && /* @__PURE__ */ i("button", {
50
50
  type: "button",
51
51
  onClick: (e) => e.stopPropagation(),
52
- className: "\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n active:scale-95\n transition-all duration-200\n ",
52
+ className: "\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n \n transition-all duration-200\n ",
53
53
  "aria-label": `${d("workspaces.card.actionsFor", "Workspace actions for")} ${o.name}`,
54
54
  children: /* @__PURE__ */ i(r, { size: 18 })
55
55
  })]
@@ -84,14 +84,14 @@ function o({ workspace: o, onClick: s, onEdit: c, onDelete: l, showActions: u =
84
84
  onClick: (e) => {
85
85
  e.stopPropagation(), c && c(o);
86
86
  },
87
- className: "\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n ",
87
+ className: "\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n \n transition-all duration-200\n font-medium\n ",
88
88
  children: d("common.edit", "Edit")
89
89
  }), l && /* @__PURE__ */ i("button", {
90
90
  type: "button",
91
91
  onClick: (e) => {
92
92
  e.stopPropagation(), l && l(o);
93
93
  },
94
- className: "\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n ",
94
+ className: "\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n \n transition-all duration-200\n font-medium\n ",
95
95
  children: d("common.delete", "Delete")
96
96
  })]
97
97
  })
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceCard.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceCard.tsx"],"sourcesContent":["import { Briefcase, MoreVertical } from 'lucide-react';\nimport { WorkspaceStatusBadge } from './WorkspaceStatusBadge';\nimport type { Workspace } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n onClick?: (workspace: Workspace) => void;\n onEdit?: (workspace: Workspace) => void;\n onDelete?: (workspace: Workspace) => void;\n showActions?: boolean;\n}\n\n/**\n * WorkspaceCard\n * Displays a workspace in a card format for grid views\n */\nexport function WorkspaceCard({\n workspace,\n onClick,\n onEdit,\n onDelete,\n showActions = true,\n}: WorkspaceCardProps) {\n const tr = useTr();\n const handleCardClick = () => {\n if (onClick) {\n onClick(workspace);\n }\n };\n\n const handleEdit = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onEdit) {\n onEdit(workspace);\n }\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onDelete) {\n onDelete(workspace);\n }\n };\n\n return (\n // react-doctor-disable-next-line react-doctor/no-static-element-interactions\n <div\n onClick={onClick ? handleCardClick : undefined}\n onKeyDown={\n onClick\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n }\n : undefined\n }\n className={`\n w-full text-left\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n p-6\n shadow-[var(--shadow-elevation-1)]\n transition-all\n ${onClick ? 'cursor-pointer hover:shadow-[var(--shadow-elevation-2)] hover:border-[var(--color-border-hover)]' : ''}\n `}\n aria-label={\n onClick ? `${tr('workspaces.card.open', 'Open workspace')} ${workspace.name}` : undefined\n }\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-12 rounded-lg bg-[var(--color-status-info-muted)] flex items-center justify-center\">\n <Briefcase size={24} className=\"text-[var(--color-status-info)]\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] text-lg\">\n {workspace.name}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)] font-mono\">/{workspace.slug}</p>\n </div>\n </div>\n\n {showActions && (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n className=\"\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n active:scale-95\n transition-all duration-200\n \"\n aria-label={`${tr('workspaces.card.actionsFor', 'Workspace actions for')} ${workspace.name}`}\n >\n <MoreVertical size={18} />\n </button>\n )}\n </div>\n\n {/* Badges */}\n <div className=\"flex gap-2 mb-4\">\n <WorkspaceStatusBadge status={workspace.status} />\n </div>\n\n {/* Member Count */}\n {workspace.memberCount !== undefined && (\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-4\">\n {workspace.memberCount}{' '}\n {workspace.memberCount === 1\n ? tr('members.role.member', 'Member').toLowerCase()\n : tr('workspaces.list.members', 'Members').toLowerCase()}\n </p>\n )}\n\n {/* Metadata */}\n <div className=\"pt-2 border-t border-[var(--color-border-default)]\">\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n {tr('common.created', 'Created')} {new Date(workspace.createdAt).toLocaleDateString()}\n </p>\n </div>\n\n {/* Footer Actions */}\n {showActions && (onEdit || onDelete) && (\n <div className=\"flex gap-2 mt-4 pt-4 border-t border-[var(--color-border-default)]\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEdit}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDelete}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAiBA,SAAgB,EAAc,EAC5B,cACA,YACA,WACA,aACA,iBAAc,MACO;CACrB,IAAM,IAAK,GAAO,EACZ,UAAwB;AAC5B,EAAI,KACF,EAAQ,EAAU;;AAkBtB,QAEE,kBAAC,OAAD;EACE,SAAS,IAAU,IAAkB,KAAA;EACrC,WACE,KACK,MAAU;AACT,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAiB;MAGrB,KAAA;EAEN,WAAW;;;;;;;;UAQP,IAAU,qGAAqG,GAAG;;EAEtH,cACE,IAAU,GAAG,EAAG,wBAAwB,iBAAiB,CAAC,GAAG,EAAU,SAAS,KAAA;EAElF,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YA1B1B;GA6BE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAoC,CAAA;MAC/D,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAU;OACR,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAgE,KAAE,EAAU,KAAS;SACjF;QACF;QAEL,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,MAAM,EAAE,iBAAiB;KACnC,WAAU;KAOV,cAAY,GAAG,EAAG,8BAA8B,wBAAwB,CAAC,GAAG,EAAU;eAEtF,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;KACnB,CAAA,CAEP;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAsB,QAAQ,EAAU,QAAU,CAAA;IAC9C,CAAA;GAGL,EAAU,gBAAgB,KAAA,KACzB,kBAAC,KAAD;IAAG,WAAU;cAAb;KACG,EAAU;KAAa;KACvB,EAAU,gBAAgB,IACvB,EAAG,uBAAuB,SAAS,CAAC,aAAa,GACjD,EAAG,2BAA2B,UAAU,CAAC,aAAa;KACxD;;GAIN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,kBAAkB,UAAU;MAAC;MAAE,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB;MACnF;;IACA,CAAA;GAGL,MAAgB,KAAU,MACzB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAxGQ,MAAwB;AAE1C,MADA,EAAE,iBAAiB,EACf,KACF,EAAO,EAAU;;KAsGT,WAAU;eAWT,EAAG,eAAe,OAAO;KACnB,CAAA,EAEV,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAnHU,MAAwB;AAE5C,MADA,EAAE,iBAAiB,EACf,KACF,EAAS,EAAU;;KAiHX,WAAU;eAWT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CAEP;;GAEJ"}
1
+ {"version":3,"file":"WorkspaceCard.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceCard.tsx"],"sourcesContent":["import { Briefcase, MoreVertical } from 'lucide-react';\nimport { WorkspaceStatusBadge } from './WorkspaceStatusBadge';\nimport type { Workspace } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceCardProps {\n workspace: Workspace;\n onClick?: (workspace: Workspace) => void;\n onEdit?: (workspace: Workspace) => void;\n onDelete?: (workspace: Workspace) => void;\n showActions?: boolean;\n}\n\n/**\n * WorkspaceCard\n * Displays a workspace in a card format for grid views\n */\nexport function WorkspaceCard({\n workspace,\n onClick,\n onEdit,\n onDelete,\n showActions = true,\n}: WorkspaceCardProps) {\n const tr = useTr();\n const handleCardClick = () => {\n if (onClick) {\n onClick(workspace);\n }\n };\n\n const handleEdit = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onEdit) {\n onEdit(workspace);\n }\n };\n\n const handleDelete = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (onDelete) {\n onDelete(workspace);\n }\n };\n\n return (\n // react-doctor-disable-next-line react-doctor/no-static-element-interactions\n <div\n onClick={onClick ? handleCardClick : undefined}\n onKeyDown={\n onClick\n ? (event) => {\n if (event.key === 'Enter' || event.key === ' ') {\n event.preventDefault();\n handleCardClick();\n }\n }\n : undefined\n }\n className={`\n w-full text-left\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n p-6\n shadow-[var(--shadow-elevation-1)]\n transition-all\n ${onClick ? 'cursor-pointer hover:shadow-[var(--shadow-elevation-2)] hover:border-[var(--color-border-hover)]' : ''}\n `}\n aria-label={\n onClick ? `${tr('workspaces.card.open', 'Open workspace')} ${workspace.name}` : undefined\n }\n role={onClick ? 'button' : undefined}\n tabIndex={onClick ? 0 : undefined}\n >\n {/* Header */}\n <div className=\"flex items-start justify-between mb-4\">\n <div className=\"flex items-center gap-3\">\n <div className=\"size-12 rounded-lg bg-[var(--color-status-info-muted)] flex items-center justify-center\">\n <Briefcase size={24} className=\"text-[var(--color-status-info)]\" />\n </div>\n <div className=\"flex-1\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] text-lg\">\n {workspace.name}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)] font-mono\">/{workspace.slug}</p>\n </div>\n </div>\n\n {showActions && (\n <button\n type=\"button\"\n onClick={(e) => e.stopPropagation()}\n className=\"\n p-1.5 rounded-lg\n text-[var(--color-text-secondary)]\n hover:bg-[var(--color-bg-subtle)]\n \n transition-all duration-200\n \"\n aria-label={`${tr('workspaces.card.actionsFor', 'Workspace actions for')} ${workspace.name}`}\n >\n <MoreVertical size={18} />\n </button>\n )}\n </div>\n\n {/* Badges */}\n <div className=\"flex gap-2 mb-4\">\n <WorkspaceStatusBadge status={workspace.status} />\n </div>\n\n {/* Member Count */}\n {workspace.memberCount !== undefined && (\n <p className=\"text-sm text-[var(--color-text-secondary)] mb-4\">\n {workspace.memberCount}{' '}\n {workspace.memberCount === 1\n ? tr('members.role.member', 'Member').toLowerCase()\n : tr('workspaces.list.members', 'Members').toLowerCase()}\n </p>\n )}\n\n {/* Metadata */}\n <div className=\"pt-2 border-t border-[var(--color-border-default)]\">\n <p className=\"text-xs text-[var(--color-text-muted)]\">\n {tr('common.created', 'Created')} {new Date(workspace.createdAt).toLocaleDateString()}\n </p>\n </div>\n\n {/* Footer Actions */}\n {showActions && (onEdit || onDelete) && (\n <div className=\"flex gap-2 mt-4 pt-4 border-t border-[var(--color-border-default)]\">\n {onEdit && (\n <button\n type=\"button\"\n onClick={handleEdit}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-bg-subtle)]\n text-[var(--color-text-primary)]\n rounded-lg\n hover:bg-[var(--color-bg-muted)]\n \n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.edit', 'Edit')}\n </button>\n )}\n {onDelete && (\n <button\n type=\"button\"\n onClick={handleDelete}\n className=\"\n flex-1 px-3 py-1.5 text-sm\n bg-[var(--color-status-error-muted)]\n text-[var(--color-status-error)]\n rounded-lg\n hover:opacity-80\n \n transition-all duration-200\n font-medium\n \"\n >\n {tr('common.delete', 'Delete')}\n </button>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;AAiBA,SAAgB,EAAc,EAC5B,cACA,YACA,WACA,aACA,iBAAc,MACO;CACrB,IAAM,IAAK,GAAO,EACZ,UAAwB;AAC5B,EAAI,KACF,EAAQ,EAAU;;AAkBtB,QAEE,kBAAC,OAAD;EACE,SAAS,IAAU,IAAkB,KAAA;EACrC,WACE,KACK,MAAU;AACT,IAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAiB;MAGrB,KAAA;EAEN,WAAW;;;;;;;;UAQP,IAAU,qGAAqG,GAAG;;EAEtH,cACE,IAAU,GAAG,EAAG,wBAAwB,iBAAiB,CAAC,GAAG,EAAU,SAAS,KAAA;EAElF,MAAM,IAAU,WAAW,KAAA;EAC3B,UAAU,IAAU,IAAI,KAAA;YA1B1B;GA6BE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAoC,CAAA;MAC/D,CAAA,EACN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAU;OACR,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAb,CAAgE,KAAE,EAAU,KAAS;SACjF;QACF;QAEL,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAAU,MAAM,EAAE,iBAAiB;KACnC,WAAU;KAOV,cAAY,GAAG,EAAG,8BAA8B,wBAAwB,CAAC,GAAG,EAAU;eAEtF,kBAAC,GAAD,EAAc,MAAM,IAAM,CAAA;KACnB,CAAA,CAEP;;GAGN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAsB,QAAQ,EAAU,QAAU,CAAA;IAC9C,CAAA;GAGL,EAAU,gBAAgB,KAAA,KACzB,kBAAC,KAAD;IAAG,WAAU;cAAb;KACG,EAAU;KAAa;KACvB,EAAU,gBAAgB,IACvB,EAAG,uBAAuB,SAAS,CAAC,aAAa,GACjD,EAAG,2BAA2B,UAAU,CAAC,aAAa;KACxD;;GAIN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eAAb;MACG,EAAG,kBAAkB,UAAU;MAAC;MAAE,IAAI,KAAK,EAAU,UAAU,CAAC,oBAAoB;MACnF;;IACA,CAAA;GAGL,MAAgB,KAAU,MACzB,kBAAC,OAAD;IAAK,WAAU;cAAf,CACG,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAxGQ,MAAwB;AAE1C,MADA,EAAE,iBAAiB,EACf,KACF,EAAO,EAAU;;KAsGT,WAAU;eAWT,EAAG,eAAe,OAAO;KACnB,CAAA,EAEV,KACC,kBAAC,UAAD;KACE,MAAK;KACL,UAnHU,MAAwB;AAE5C,MADA,EAAE,iBAAiB,EACf,KACF,EAAS,EAAU;;KAiHX,WAAU;eAWT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CAEP;;GAEJ"}
@@ -325,7 +325,7 @@ function g({ workspace: g, tenantId: _, organizationId: v, organizationName: y,
325
325
  children: [/* @__PURE__ */ h("button", {
326
326
  type: "submit",
327
327
  disabled: C,
328
- className: "inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 hover:scale-[1.02] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]",
328
+ className: "inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]",
329
329
  children: [C ? /* @__PURE__ */ m(f, {
330
330
  size: 16,
331
331
  className: "animate-spin"
@@ -334,7 +334,7 @@ function g({ workspace: g, tenantId: _, organizationId: v, organizationName: y,
334
334
  type: "button",
335
335
  onClick: S,
336
336
  disabled: C,
337
- className: "rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]",
337
+ className: "rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]",
338
338
  children: w("common.cancel", "Cancel")
339
339
  })]
340
340
  })
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || workspace?.tenantId || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between. With a single shared tenant there is nothing to\n // choose, so the picker is hidden and the tenant is auto-selected.\n const showTenantPicker = shouldLoadTenants && tenants.length > 1;\n\n // Auto-select the shared (or only) tenant when there is no explicit tenant\n // and no real choice to make, so the workspace lands in the shared tenant\n // without surfacing a picker.\n useEffect(() => {\n if (!shouldLoadTenants || formData.tenantId || tenants.length === 0) {\n return;\n }\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const resolved = sharedTenant?.id ?? tenants[0]?.id;\n if (resolved) {\n setFormData((previousState) =>\n previousState.tenantId ? previousState : { ...previousState, tenantId: resolved }\n );\n }\n }, [shouldLoadTenants, tenants, formData.tenantId]);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n {/* Tenant picker — shown ONLY when the user has more than one tenant\n to choose between. For single-tenant (shared) users the tenant is\n auto-selected and no picker is rendered. */}\n {showTenantPicker ? (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n aria-label={tr('common.organization', 'Organization')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace 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 aria-label={tr('workspaces.form.name', 'Workspace Name')}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\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={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\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 ) : null}\n </div>\n\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=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 hover:scale-[1.02] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || workspace?.tenantId || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between. With a single shared tenant there is nothing to\n // choose, so the picker is hidden and the tenant is auto-selected.\n const showTenantPicker = shouldLoadTenants && tenants.length > 1;\n\n // Auto-select the shared (or only) tenant when there is no explicit tenant\n // and no real choice to make, so the workspace lands in the shared tenant\n // without surfacing a picker.\n useEffect(() => {\n if (!shouldLoadTenants || formData.tenantId || tenants.length === 0) {\n return;\n }\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const resolved = sharedTenant?.id ?? tenants[0]?.id;\n if (resolved) {\n setFormData((previousState) =>\n previousState.tenantId ? previousState : { ...previousState, tenantId: resolved }\n );\n }\n }, [shouldLoadTenants, tenants, formData.tenantId]);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n {/* Tenant picker — shown ONLY when the user has more than one tenant\n to choose between. For single-tenant (shared) users the tenant is\n auto-selected and no picker is rendered. */}\n {showTenantPicker ? (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n aria-label={tr('common.organization', 'Organization')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace 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 aria-label={tr('workspaces.form.name', 'Workspace Name')}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\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={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\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 ) : null}\n </div>\n\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=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
@@ -30,14 +30,14 @@ function f() {
30
30
  className: "min-h-full bg-[var(--color-bg-canvas)] p-6",
31
31
  children: /* @__PURE__ */ l(i, { message: f("common.loadingPermissions", "Loading permissions...") })
32
32
  }) : /* @__PURE__ */ u("div", {
33
- className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300",
33
+ className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200",
34
34
  children: [/* @__PURE__ */ u("div", {
35
35
  className: "mb-6",
36
36
  children: [
37
37
  /* @__PURE__ */ u("button", {
38
38
  type: "button",
39
39
  onClick: S,
40
- 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 ",
40
+ className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n ",
41
41
  children: [/* @__PURE__ */ l(s, { size: 20 }), /* @__PURE__ */ l("span", {
42
42
  className: "text-sm font-medium",
43
43
  children: y ? `${f("common.backTo", "Back to")} ${b?.name || f("common.organization", "Organization")}` : f("workspaces.create.backToWorkspaces", "Back to Workspaces")
@@ -1 +1 @@
1
- {"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\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 // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\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=\"mb-6\">\n <button\n 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 {organizationIdFromUrl\n ? `${tr('common.backTo', 'Back to')} ${organization?.name || tr('common.organization', 'Organization')}`\n : tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.create.title', 'Create Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {organizationIdFromUrl && organization\n ? `${tr('workspaces.create.subtitleForOrg', 'Add a new workspace to')} ${organization.name}`\n : tr('workspaces.create.subtitle', 'Add a new workspace to an organization')}\n </p>\n </div>\n\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 <WorkspaceForm\n tenantId={organization?.tenantId}\n tenantName={organization?.tenantId}\n organizationId={organizationIdFromUrl}\n organizationName={organization?.name}\n showOrganizationSelector={!organizationIdFromUrl}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAM3D,EAAE,WAAW,MAAuB,GAAgB,EACpD,EAAE,oBAAiB,eAAY,GAAoB,EACnD,CAAC,KAAgB,GAAiB,EAGlC,IAAwB,EAAa,IAAI,iBAAiB,IAAI,KAAA,GAG9D,EAAE,oBAAiB,EAAgB,KAAyB,GAAG,EAE/D,IAAe,OAAO,MAAsD;AAChF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EAC6B;AAChD,OAAI,EAAO,MAAM,gBAEf,CADA,EAAM,QAAQ,EAAG,6BAA6B,iCAAiC,CAAC,EAE9E,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,cAAc,EAAO,KAAK,gBAAgB,KAAK;YAE7D,EAAO,UAAU,EAAO,OAAO,SAAS,GAAG;IAEpD,IAAM,IACJ,EAAO,OAAO,IAAI,WAAW,EAAG,4BAA4B,6BAA6B;AAG3F,IAFkB,EAAO,OAAO,IAAI,YAAY,SAE9B,oBAChB,EAAM,MAAM,EAAG,uBAAuB,gDAAgD,CAAC,GAEvF,EAAM,MAAM,EAAa;;WAGtB,GAAO;GACd,IAAM,IACJ,aAAiB,QACb,EAAM,UACN,EAAG,0BAA0B,+BAA+B;AAClE,KAAM,MAAM,EAAa;;IAIvB,UAAqB;AAEzB,EACE,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,aAAa;;AAuBtC,QAnBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAgBR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,IACG,GAAG,EAAG,iBAAiB,UAAU,CAAC,GAAG,GAAc,QAAQ,EAAG,uBAAuB,eAAe,KACpG,EAAG,sCAAsC,qBAAqB;MAC7D,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,2BAA2B,mBAAmB;KAC/C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,KAAyB,IACtB,GAAG,EAAG,oCAAoC,yBAAyB,CAAC,GAAG,EAAa,SACpF,EAAG,8BAA8B,yCAAyC;KAC5E,CAAA;IACA;MAEN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,GAAc;KACxB,YAAY,GAAc;KAC1B,gBAAgB;KAChB,kBAAkB,GAAc;KAChC,0BAA0B,CAAC;KAC3B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF"}
1
+ {"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\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 // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\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-200\">\n <div className=\"mb-6\">\n <button\n 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 \n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {organizationIdFromUrl\n ? `${tr('common.backTo', 'Back to')} ${organization?.name || tr('common.organization', 'Organization')}`\n : tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.create.title', 'Create Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {organizationIdFromUrl && organization\n ? `${tr('workspaces.create.subtitleForOrg', 'Add a new workspace to')} ${organization.name}`\n : tr('workspaces.create.subtitle', 'Add a new workspace to an organization')}\n </p>\n </div>\n\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 <WorkspaceForm\n tenantId={organization?.tenantId}\n tenantName={organization?.tenantId}\n organizationId={organizationIdFromUrl}\n organizationName={organization?.name}\n showOrganizationSelector={!organizationIdFromUrl}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAM3D,EAAE,WAAW,MAAuB,GAAgB,EACpD,EAAE,oBAAiB,eAAY,GAAoB,EACnD,CAAC,KAAgB,GAAiB,EAGlC,IAAwB,EAAa,IAAI,iBAAiB,IAAI,KAAA,GAG9D,EAAE,oBAAiB,EAAgB,KAAyB,GAAG,EAE/D,IAAe,OAAO,MAAsD;AAChF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EAC6B;AAChD,OAAI,EAAO,MAAM,gBAEf,CADA,EAAM,QAAQ,EAAG,6BAA6B,iCAAiC,CAAC,EAE9E,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,cAAc,EAAO,KAAK,gBAAgB,KAAK;YAE7D,EAAO,UAAU,EAAO,OAAO,SAAS,GAAG;IAEpD,IAAM,IACJ,EAAO,OAAO,IAAI,WAAW,EAAG,4BAA4B,6BAA6B;AAG3F,IAFkB,EAAO,OAAO,IAAI,YAAY,SAE9B,oBAChB,EAAM,MAAM,EAAG,uBAAuB,gDAAgD,CAAC,GAEvF,EAAM,MAAM,EAAa;;WAGtB,GAAO;GACd,IAAM,IACJ,aAAiB,QACb,EAAM,UACN,EAAG,0BAA0B,+BAA+B;AAClE,KAAM,MAAM,EAAa;;IAIvB,UAAqB;AAEzB,EACE,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,aAAa;;AAuBtC,QAnBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAgBR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,IACG,GAAG,EAAG,iBAAiB,UAAU,CAAC,GAAG,GAAc,QAAQ,EAAG,uBAAuB,eAAe,KACpG,EAAG,sCAAsC,qBAAqB;MAC7D,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,2BAA2B,mBAAmB;KAC/C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,KAAyB,IACtB,GAAG,EAAG,oCAAoC,yBAAyB,CAAC,GAAG,EAAa,SACpF,EAAG,8BAA8B,yCAAyC;KAC5E,CAAA;IACA;MAEN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,GAAc;KACxB,YAAY,GAAc;KAC1B,gBAAgB;KAChB,kBAAkB,GAAc;KAChC,0BAA0B,CAAC;KAC3B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF"}