@burdenoff/microfe-organization 2026.525.3 → 2026.527.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/organization-management/AdminOrganizationRoutes.js +1 -1
- package/dist/organization-management/AdminOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/UserOrganizationRoutes.js +1 -1
- package/dist/organization-management/UserOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -0
- package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -0
- package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
- package/dist/organization-management/modules/members/components/PendingInvitesList.js +2 -0
- package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js +1 -0
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js +2 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MembersListPage.js +10 -2
- package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js +6 -0
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
- package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +1 -0
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +6 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +1 -0
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +15 -4
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +5 -5
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +6 -2
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaceMembers.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaces.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +1 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +8 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +1 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +10 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
- package/dist/organization-management/providers/OrgConsoleProvider.js +1 -1
- package/dist/organization-management/providers/OrgConsoleProvider.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js +25 -86
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js +69 -0
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js.map +1 -0
- package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -0
- package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js +2 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -2
- package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
- package/dist/organization-management/shared/components/input/SearchInput.js +1 -0
- package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js +1 -1
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js.map +1 -1
- package/dist/organization-management/shared/types/graphql.js.map +1 -1
- package/package.json +2 -1
package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Briefcase, Building, Edit, Users } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useOrganization } from '../hooks/useOrganizations';\nimport { OrganizationStatusBadge } from '../components/OrganizationStatusBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { useWorkspacesByOrganization } from '../../workspaces/hooks/useWorkspaces';\nimport { WorkspaceCard } from '../../workspaces/components/WorkspaceCard';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\n\ntype ActiveTab = 'overview' | 'workspaces';\n\nfunction formatDate(dateString: string) {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function OrganizationDetailPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n useOrganizationPageView('organizations.detail', { organizationId: id });\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { organization, loading, error } = useOrganization(id ?? '');\n const [activeTab, setActiveTab] = useState<ActiveTab>('overview');\n\n const shouldFetchWorkspaces = activeTab === 'workspaces' && Boolean(id);\n const {\n workspaces,\n loading: workspacesLoading,\n error: workspacesError,\n refetch: refetchWorkspaces,\n } = useWorkspacesByOrganization(shouldFetchWorkspaces ? (id ?? '') : '', 50, 0);\n\n const tabItems = useMemo(\n () => [\n { id: 'overview' as const, label: tr('organizations.detail.tab.overview', 'Overview') },\n {\n id: 'workspaces' as const,\n label: `${tr('organizations.detail.tab.workspaces', 'Workspaces')} (${activeTab === 'workspaces' ? workspaces.length : (organization?.workspaceCount ?? 0)})`,\n },\n ],\n [activeTab, organization?.workspaceCount, tr, workspaces.length]\n );\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_READ)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.detail.noPermission',\n \"You don't have permission to view this organization.\"\n )}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState\n message={tr('organizations.detail.loading', 'Loading organization details...')}\n />\n </div>\n );\n }\n\n if (error || !organization) {\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 || tr('organizations.detail.notFound', 'Organization not found')}\n action={{\n label: tr('organizations.create.back', 'Back to Organizations'),\n onClick: () => navigate(basePath),\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=\"space-y-4\">\n <button\n onClick={() => navigate(basePath)}\n className=\"inline-flex min-h-10 items-center gap-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:text-[var(--color-text-primary)] active:scale-[0.98]\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n <span className=\"text-sm font-medium\">\n {tr('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <div className=\"flex flex-col gap-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5 shadow-[var(--shadow-elevation-1)] lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"flex min-w-0 items-start gap-4\">\n <div className=\"flex h-16 w-16 items-center justify-center rounded-xl bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Building size={28} aria-hidden=\"true\" />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-start gap-3\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {organization.name}\n </h1>\n <OrganizationStatusBadge status={organization.status} />\n </div>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n /{organization.slug}\n </p>\n {organization.description ? (\n <p className=\"max-w-2xl text-sm text-[var(--color-text-secondary)]\">\n {organization.description}\n </p>\n ) : null}\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n {hasPermission(PERMISSIONS.MEMBER_LIST) && (\n <button\n onClick={() => navigate(`${basePath}/${id}/members`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] px-4 py-2.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n >\n <Users size={16} aria-hidden=\"true\" />\n <span>\n {hasPermission(PERMISSIONS.MEMBER_INVITE)\n ? tr('organizations.detail.manageMembers', 'Manage Members')\n : tr('organizations.detail.viewMembers', 'View Members')}\n </span>\n </button>\n )}\n {hasPermission(PERMISSIONS.ORG_UPDATE) && (\n <button\n onClick={() => navigate(`${basePath}/${id}/edit`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Edit size={16} aria-hidden=\"true\" />\n <span>{tr('organizations.edit.title', 'Edit Organization')}</span>\n </button>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"overflow-x-auto border-b border-[var(--color-border-default)]\">\n <div className=\"flex min-w-max gap-2 px-4 pt-4 sm:px-6\">\n {tabItems.map((tabItem) => {\n const isActive = activeTab === tabItem.id;\n return (\n <button\n key={tabItem.id}\n onClick={() => setActiveTab(tabItem.id)}\n className={`inline-flex min-h-11 items-center rounded-t-lg border-b-2 px-4 py-3 text-sm font-medium transition-colors ${\n isActive\n ? 'border-[var(--color-action-primary-bg)] text-[var(--color-action-primary-bg)]'\n : 'border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'\n }`}\n aria-pressed={isActive}\n >\n {tabItem.label}\n </button>\n );\n })}\n </div>\n </div>\n\n <div className=\"p-4 sm:p-6\">\n {activeTab === 'overview' ? (\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_320px]\">\n <section className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-5\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('organizations.detail.detailsSection', 'Organization details')}\n </h2>\n\n <dl className=\"mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.name', 'Name')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {organization.name}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.slug', 'Slug')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {organization.slug}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.status', 'Status')}\n </dt>\n <dd className=\"mt-1\">\n <OrganizationStatusBadge status={organization.status} />\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.workspaceCount', 'Workspace count')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </dd>\n </div>\n {organization.website ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.website', 'Website')}\n </dt>\n <dd className=\"mt-1 break-all text-sm\">\n <a\n href={organization.website}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-[var(--color-action-primary-bg)] underline-offset-2 hover:underline\"\n >\n {organization.website}\n </a>\n </dd>\n </div>\n ) : null}\n {organization.description ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.description', 'Description')}\n </dt>\n <dd className=\"mt-1 text-sm leading-6 text-[var(--color-text-primary)]\">\n {organization.description}\n </dd>\n </div>\n ) : null}\n </dl>\n </section>\n\n <aside className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-5\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('common.metadata', 'Metadata')}\n </h2>\n\n <dl className=\"mt-5 space-y-4\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.created', 'Created')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {formatDate(organization.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.lastUpdated', 'Last updated')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {formatDate(organization.updatedAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.tenantId', 'Tenant ID')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {organization.tenantId}\n </dd>\n </div>\n </dl>\n </aside>\n </div>\n ) : (\n <section className=\"space-y-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('organizations.detail.workspacesTitle', 'Organization workspaces')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.detail.workspacesSubtitle',\n 'Create and manage workspaces for this organization.'\n )}\n </p>\n </div>\n\n {hasPermission(PERMISSIONS.WORKSPACE_CREATE) && (\n <button\n onClick={() => navigate(`${basePath}/workspaces/new?organizationId=${id}`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Briefcase size={16} aria-hidden=\"true\" />\n <span>{tr('workspaces.create.button', 'Create Workspace')}</span>\n </button>\n )}\n </div>\n\n {workspacesLoading ? (\n <div className=\"py-8\">\n <LoadingState\n message={tr('workspaces.list.loading', 'Loading workspaces...')}\n />\n </div>\n ) : workspacesError ? (\n <ErrorState\n message={workspacesError.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetchWorkspaces();\n },\n }}\n />\n ) : workspaces.length > 0 ? (\n <div className=\"grid grid-cols-1 gap-4 xl:grid-cols-2\">\n {workspaces.map((workspace) => (\n <WorkspaceCard\n key={workspace.id}\n workspace={workspace}\n showActions={false}\n onClick={(selectedWorkspace) => {\n navigate(`${basePath}/workspaces/${selectedWorkspace.id}`);\n }}\n />\n ))}\n </div>\n ) : (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-6\">\n <EmptyState\n icon={<Briefcase size={40} aria-hidden=\"true\" />}\n title={tr('workspaces.list.emptyTitle', 'No workspaces yet')}\n description={\n hasPermission(PERMISSIONS.WORKSPACE_CREATE)\n ? tr(\n 'workspaces.list.emptyDescription',\n 'Create a workspace when this organization is ready to collaborate with teams or projects.'\n )\n : tr(\n 'workspaces.list.emptyDescriptionViewer',\n 'No workspaces have been created in this organization yet.'\n )\n }\n action={\n hasPermission(PERMISSIONS.WORKSPACE_CREATE)\n ? {\n label: tr('workspaces.create.button', 'Create Workspace'),\n onClick: () =>\n navigate(`${basePath}/workspaces/new?organizationId=${id}`),\n }\n : undefined\n }\n />\n </div>\n )}\n </section>\n )}\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrganizationDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Briefcase, Building, Edit, Users } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useOrganization } from '../hooks/useOrganizations';\nimport { OrganizationStatusBadge } from '../components/OrganizationStatusBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { useWorkspacesByOrganization } from '../../workspaces/hooks/useWorkspaces';\nimport { WorkspaceCard } from '../../workspaces/components/WorkspaceCard';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\n\ntype ActiveTab = 'overview' | 'workspaces';\n\nfunction formatDate(dateString: string) {\n return new Date(dateString).toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nexport function OrganizationDetailPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n useOrganizationPageView('organizations.detail', { organizationId: id });\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { organization, loading, error } = useOrganization(id ?? '');\n const [activeTab, setActiveTab] = useState<ActiveTab>('overview');\n\n const shouldFetchWorkspaces = activeTab === 'workspaces' && Boolean(id);\n const {\n workspaces,\n loading: workspacesLoading,\n error: workspacesError,\n refetch: refetchWorkspaces,\n } = useWorkspacesByOrganization(shouldFetchWorkspaces ? (id ?? '') : '', 50, 0);\n\n const tabItems = useMemo(\n () => [\n { id: 'overview' as const, label: tr('organizations.detail.tab.overview', 'Overview') },\n {\n id: 'workspaces' as const,\n label: `${tr('organizations.detail.tab.workspaces', 'Workspaces')} (${activeTab === 'workspaces' ? workspaces.length : (organization?.workspaceCount ?? 0)})`,\n },\n ],\n [activeTab, organization?.workspaceCount, tr, workspaces.length]\n );\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_READ)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.detail.noPermission',\n \"You don't have permission to view this organization.\"\n )}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState\n message={tr('organizations.detail.loading', 'Loading organization details...')}\n />\n </div>\n );\n }\n\n if (error || !organization) {\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 || tr('organizations.detail.notFound', 'Organization not found')}\n action={{\n label: tr('organizations.create.back', 'Back to Organizations'),\n onClick: () => navigate(basePath),\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=\"space-y-4\">\n <button type=\"button\"\n onClick={() => navigate(basePath)}\n className=\"inline-flex min-h-10 items-center gap-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:text-[var(--color-text-primary)] active:scale-[0.98]\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n <span className=\"text-sm font-medium\">\n {tr('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <div className=\"flex flex-col gap-4 rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5 shadow-[var(--shadow-elevation-1)] lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"flex min-w-0 items-start gap-4\">\n <div className=\"flex size-16 items-center justify-center rounded-xl bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]\">\n <Building size={28} aria-hidden=\"true\" />\n </div>\n\n <div className=\"min-w-0 space-y-2\">\n <div className=\"flex flex-wrap items-start gap-3\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {organization.name}\n </h1>\n <OrganizationStatusBadge status={organization.status} />\n </div>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n /{organization.slug}\n </p>\n {organization.description ? (\n <p className=\"max-w-2xl text-sm text-[var(--color-text-secondary)]\">\n {organization.description}\n </p>\n ) : null}\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n {hasPermission(PERMISSIONS.MEMBER_LIST) && (\n <button type=\"button\"\n onClick={() => navigate(`${basePath}/${id}/members`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] px-4 py-2.5 text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n >\n <Users size={16} aria-hidden=\"true\" />\n <span>\n {hasPermission(PERMISSIONS.MEMBER_INVITE)\n ? tr('organizations.detail.manageMembers', 'Manage Members')\n : tr('organizations.detail.viewMembers', 'View Members')}\n </span>\n </button>\n )}\n {hasPermission(PERMISSIONS.ORG_UPDATE) && (\n <button type=\"button\"\n onClick={() => navigate(`${basePath}/${id}/edit`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Edit size={16} aria-hidden=\"true\" />\n <span>{tr('organizations.edit.title', 'Edit Organization')}</span>\n </button>\n )}\n </div>\n </div>\n </div>\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)]\">\n <div className=\"overflow-x-auto border-b border-[var(--color-border-default)]\">\n <div className=\"flex min-w-max gap-2 px-4 pt-4 sm:px-6\">\n {tabItems.map((tabItem) => {\n const isActive = activeTab === tabItem.id;\n return (\n <button type=\"button\"\n key={tabItem.id}\n onClick={() => setActiveTab(tabItem.id)}\n className={`inline-flex min-h-11 items-center rounded-t-lg border-b-2 px-4 py-3 text-sm font-medium transition-colors ${\n isActive\n ? 'border-[var(--color-action-primary-bg)] text-[var(--color-action-primary-bg)]'\n : 'border-transparent text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)]'\n }`}\n aria-pressed={isActive}\n >\n {tabItem.label}\n </button>\n );\n })}\n </div>\n </div>\n\n <div className=\"p-4 sm:p-6\">\n {activeTab === 'overview' ? (\n <div className=\"grid grid-cols-1 gap-6 xl:grid-cols-[minmax(0,1fr)_320px]\">\n <section className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-5\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('organizations.detail.detailsSection', 'Organization details')}\n </h2>\n\n <dl className=\"mt-5 grid grid-cols-1 gap-4 sm:grid-cols-2\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.name', 'Name')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {organization.name}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.slug', 'Slug')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {organization.slug}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.status', 'Status')}\n </dt>\n <dd className=\"mt-1\">\n <OrganizationStatusBadge status={organization.status} />\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.workspaceCount', 'Workspace count')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {organization.workspaceCount ?? 0}\n </dd>\n </div>\n {organization.website ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.website', 'Website')}\n </dt>\n <dd className=\"mt-1 break-all text-sm\">\n <a\n href={organization.website}\n target=\"_blank\"\n rel=\"noreferrer\"\n className=\"text-[var(--color-action-primary-bg)] underline-offset-2 hover:underline\"\n >\n {organization.website}\n </a>\n </dd>\n </div>\n ) : null}\n {organization.description ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.description', 'Description')}\n </dt>\n <dd className=\"mt-1 text-sm leading-6 text-[var(--color-text-primary)]\">\n {organization.description}\n </dd>\n </div>\n ) : null}\n </dl>\n </section>\n\n <aside className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-5\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('common.metadata', 'Metadata')}\n </h2>\n\n <dl className=\"mt-5 space-y-4\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.created', 'Created')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {formatDate(organization.createdAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('common.lastUpdated', 'Last updated')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {formatDate(organization.updatedAt)}\n </dd>\n </div>\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('organizations.detail.tenantId', 'Tenant ID')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {organization.tenantId}\n </dd>\n </div>\n </dl>\n </aside>\n </div>\n ) : (\n <section className=\"space-y-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div>\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)]\">\n {tr('organizations.detail.workspacesTitle', 'Organization workspaces')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.detail.workspacesSubtitle',\n 'Create and manage workspaces for this organization.'\n )}\n </p>\n </div>\n\n {hasPermission(PERMISSIONS.WORKSPACE_CREATE) && (\n <button type=\"button\"\n onClick={() => navigate(`${basePath}/workspaces/new?organizationId=${id}`)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Briefcase size={16} aria-hidden=\"true\" />\n <span>{tr('workspaces.create.button', 'Create Workspace')}</span>\n </button>\n )}\n </div>\n\n {workspacesLoading ? (\n <div className=\"py-8\">\n <LoadingState\n message={tr('workspaces.list.loading', 'Loading workspaces...')}\n />\n </div>\n ) : workspacesError ? (\n <ErrorState\n message={workspacesError.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetchWorkspaces();\n },\n }}\n />\n ) : workspaces.length > 0 ? (\n <div className=\"grid grid-cols-1 gap-4 xl:grid-cols-2\">\n {workspaces.map((workspace) => (\n <WorkspaceCard\n key={workspace.id}\n workspace={workspace}\n showActions={false}\n onClick={(selectedWorkspace) => {\n navigate(`${basePath}/workspaces/${selectedWorkspace.id}`);\n }}\n />\n ))}\n </div>\n ) : (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-6\">\n <EmptyState\n icon={<Briefcase size={40} aria-hidden=\"true\" />}\n title={tr('workspaces.list.emptyTitle', 'No workspaces yet')}\n description={\n hasPermission(PERMISSIONS.WORKSPACE_CREATE)\n ? tr(\n 'workspaces.list.emptyDescription',\n 'Create a workspace when this organization is ready to collaborate with teams or projects.'\n )\n : tr(\n 'workspaces.list.emptyDescriptionViewer',\n 'No workspaces have been created in this organization yet.'\n )\n }\n action={\n hasPermission(PERMISSIONS.WORKSPACE_CREATE)\n ? {\n label: tr('workspaces.create.button', 'Create Workspace'),\n onClick: () =>\n navigate(`${basePath}/workspaces/new?organizationId=${id}`),\n }\n : undefined\n }\n />\n </div>\n )}\n </section>\n )}\n </div>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -47,6 +47,7 @@ function h() {
|
|
|
47
47
|
className: "mb-6",
|
|
48
48
|
children: [
|
|
49
49
|
/* @__PURE__ */ p("button", {
|
|
50
|
+
type: "button",
|
|
50
51
|
onClick: D,
|
|
51
52
|
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 ",
|
|
52
53
|
children: [/* @__PURE__ */ f(u, { size: 20 }), /* @__PURE__ */ p("span", {
|
package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationEditPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationEditPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useOrganization, useUpdateOrganization } from '../hooks/useOrganizations';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Edit Page\n * Form to edit an existing organization\n */\nexport function OrganizationEditPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n useOrganizationPageView('organizations.edit', { organizationId: id });\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { organization, loading: fetchLoading, error } = useOrganization(id!);\n const { updateOrganization, loading: updateLoading } = useUpdateOrganization();\n\n const handleSubmit = async (data: UpdateOrganizationInput) => {\n if (!id) return;\n\n try {\n const result = await updateOrganization(id, data);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.edit.failed', 'Failed to update organization')\n );\n }\n\n toast.success(tr('organizations.edit.success', 'Organization updated successfully'));\n navigate(`${basePath}/${id}`);\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.edit.failed', 'Failed to update organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${id}`);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_UPDATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.edit.noPermission',\n \"You don't have permission to edit organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n if (fetchLoading) {\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.edit.loading', 'Loading organization...')} />\n </div>\n );\n }\n\n if (error || !organization) {\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 || tr('organizations.detail.notFound', 'Organization not found')}\n action={{\n label: tr('organizations.create.back', 'Back to Organizations'),\n onClick: () => navigate(basePath),\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 {/* Page Header */}\n <div className=\"mb-6\">\n <button\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('common.backTo', 'Back to')} {organization.name}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.edit.title', 'Edit Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.edit.subtitle', 'Update organization information')}\n </p>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n organization={organization}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={updateLoading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,SAAgB,IAAuB;CACrC,IAAM,IAAK,GAAO,EACZ,EAAE,UAAO,GAA2B;AAC1C,GAAwB,sBAAsB,EAAE,gBAAgB,GAAI,CAAC;CACrE,IAAM,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,SAAS,GAAc,aAAU,EAAgB,EAAI,EACrE,EAAE,uBAAoB,SAAS,MAAkB,GAAuB,EAExE,IAAe,OAAO,MAAkC;AACvD,QAEL,KAAI;GACF,IAAM,IAAS,MAAM,EAAmB,GAAI,EAAK;AACjD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,6BAA6B,gCAAgC,CACnE;AAIH,GADA,EAAM,QAAQ,EAAG,8BAA8B,oCAAoC,CAAC,EACpF,EAAS,GAAG,EAAS,GAAG,IAAK;WACtB,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,6BAA6B,gCAAgC,CACrE;;IAIC,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,IAAK;;AA8C/B,QA3CI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAatC,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,8BAA8B,0BAA0B,EAAI,CAAA;EAClF,CAAA,GAIN,KAAS,CAAC,IAEV,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,SAAS,GAAO,WAAW,EAAG,iCAAiC,yBAAyB;GACxF,QAAQ;IACN,OAAO,EAAG,6BAA6B,wBAAwB;IAC/D,eAAe,EAAS,EAAS;IAClC;GACD,CAAA;EACE,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;
|
|
1
|
+
{"version":3,"file":"OrganizationEditPage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationEditPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useOrganization, useUpdateOrganization } from '../hooks/useOrganizations';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Edit Page\n * Form to edit an existing organization\n */\nexport function OrganizationEditPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n useOrganizationPageView('organizations.edit', { organizationId: id });\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { organization, loading: fetchLoading, error } = useOrganization(id!);\n const { updateOrganization, loading: updateLoading } = useUpdateOrganization();\n\n const handleSubmit = async (data: UpdateOrganizationInput) => {\n if (!id) return;\n\n try {\n const result = await updateOrganization(id, data);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.edit.failed', 'Failed to update organization')\n );\n }\n\n toast.success(tr('organizations.edit.success', 'Organization updated successfully'));\n navigate(`${basePath}/${id}`);\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.edit.failed', 'Failed to update organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${id}`);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_UPDATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.edit.noPermission',\n \"You don't have permission to edit organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n if (fetchLoading) {\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.edit.loading', 'Loading organization...')} />\n </div>\n );\n }\n\n if (error || !organization) {\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 || tr('organizations.detail.notFound', 'Organization not found')}\n action={{\n label: tr('organizations.create.back', 'Back to Organizations'),\n onClick: () => navigate(basePath),\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 {/* Page Header */}\n <div className=\"mb-6\">\n <button type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('common.backTo', 'Back to')} {organization.name}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.edit.title', 'Edit Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.edit.subtitle', 'Update organization information')}\n </p>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n organization={organization}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={updateLoading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,SAAgB,IAAuB;CACrC,IAAM,IAAK,GAAO,EACZ,EAAE,UAAO,GAA2B;AAC1C,GAAwB,sBAAsB,EAAE,gBAAgB,GAAI,CAAC;CACrE,IAAM,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,SAAS,GAAc,aAAU,EAAgB,EAAI,EACrE,EAAE,uBAAoB,SAAS,MAAkB,GAAuB,EAExE,IAAe,OAAO,MAAkC;AACvD,QAEL,KAAI;GACF,IAAM,IAAS,MAAM,EAAmB,GAAI,EAAK;AACjD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,6BAA6B,gCAAgC,CACnE;AAIH,GADA,EAAM,QAAQ,EAAG,8BAA8B,oCAAoC,CAAC,EACpF,EAAS,GAAG,EAAS,GAAG,IAAK;WACtB,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,6BAA6B,gCAAgC,CACrE;;IAIC,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,IAAK;;AA8C/B,QA3CI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAatC,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,8BAA8B,0BAA0B,EAAI,CAAA;EAClF,CAAA,GAIN,KAAS,CAAC,IAEV,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,SAAS,GAAO,WAAW,EAAG,iCAAiC,yBAAyB;GACxF,QAAQ;IACN,OAAO,EAAG,6BAA6B,wBAAwB;IAC/D,eAAe,EAAS,EAAS;IAClC;GACD,CAAA;EACE,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KAAQ,MAAK;KACX,SAAS;KACT,WAAU;eAFZ,CAUE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAG,iBAAiB,UAAU;OAAC;OAAE,EAAa;OAC1C;QACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,4BAA4B,oBAAoB;KACjD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,+BAA+B,kCAAkC;KACnE,CAAA;IACA;MAGN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACgB;KACd,UAAU;KACV,UAAU;KACV,SAAS;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MAxEJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,mCACA,mDACD;GACG,CAAA;EACF,CAAA"}
|
|
@@ -127,6 +127,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
127
127
|
}), /* @__PURE__ */ T("div", {
|
|
128
128
|
className: "flex flex-wrap items-center gap-2",
|
|
129
129
|
children: [D === "my" && /* @__PURE__ */ T("button", {
|
|
130
|
+
type: "button",
|
|
130
131
|
onClick: () => k(`${A}/invites`),
|
|
131
132
|
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]",
|
|
132
133
|
"aria-label": O("organizations.invitations.button", "Invitations"),
|
|
@@ -146,6 +147,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
146
147
|
})
|
|
147
148
|
]
|
|
148
149
|
}), N && j(d.ORG_CREATE) && /* @__PURE__ */ T("button", {
|
|
150
|
+
type: "button",
|
|
149
151
|
onClick: () => k(`${A}/new`),
|
|
150
152
|
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]",
|
|
151
153
|
children: [/* @__PURE__ */ w(le, {
|
|
@@ -170,6 +172,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
170
172
|
inputId: "organizations-search",
|
|
171
173
|
className: "flex-1"
|
|
172
174
|
}), /* @__PURE__ */ w("button", {
|
|
175
|
+
type: "button",
|
|
173
176
|
onClick: () => {
|
|
174
177
|
W();
|
|
175
178
|
},
|
|
@@ -194,6 +197,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
194
197
|
ariaLabel: O("organizations.list.selectAll", "Select all organizations")
|
|
195
198
|
}), /* @__PURE__ */ w("span", { children: O("common.selectAllVisible", "Select all visible") })]
|
|
196
199
|
}), L.size > 0 ? /* @__PURE__ */ w("button", {
|
|
200
|
+
type: "button",
|
|
197
201
|
onClick: () => R(/* @__PURE__ */ new Set()),
|
|
198
202
|
className: "text-sm text-[var(--color-text-secondary)] transition-colors hover:text-[var(--color-text-primary)]",
|
|
199
203
|
children: O("common.clearSelection", "Clear selection")
|
|
@@ -208,6 +212,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
208
212
|
O("common.selected", "selected")
|
|
209
213
|
]
|
|
210
214
|
}), j(d.ORG_DELETE) && /* @__PURE__ */ T("button", {
|
|
215
|
+
type: "button",
|
|
211
216
|
onClick: ve,
|
|
212
217
|
disabled: q.length === 0 || K,
|
|
213
218
|
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",
|
|
@@ -267,9 +272,9 @@ function D({ mode: D = "my" } = {}) {
|
|
|
267
272
|
children: [e.logo ? /* @__PURE__ */ w("img", {
|
|
268
273
|
src: e.logo,
|
|
269
274
|
alt: `${e.name} ${O("common.logo", "logo")}`,
|
|
270
|
-
className: "
|
|
275
|
+
className: "size-12 rounded-lg object-cover"
|
|
271
276
|
}) : /* @__PURE__ */ w("div", {
|
|
272
|
-
className: "flex
|
|
277
|
+
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)]",
|
|
273
278
|
children: e.name[0]
|
|
274
279
|
}), /* @__PURE__ */ T("div", {
|
|
275
280
|
className: "min-w-0 flex-1",
|
|
@@ -308,6 +313,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
308
313
|
className: "mt-4 flex flex-wrap gap-2 border-t border-[var(--color-border-default)] pt-4",
|
|
309
314
|
children: [
|
|
310
315
|
/* @__PURE__ */ T("button", {
|
|
316
|
+
type: "button",
|
|
311
317
|
onClick: () => k(`${A}/${e.id}`),
|
|
312
318
|
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)]",
|
|
313
319
|
children: [/* @__PURE__ */ w(y, {
|
|
@@ -316,6 +322,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
316
322
|
}), O("common.view", "View")]
|
|
317
323
|
}),
|
|
318
324
|
j(d.ORG_UPDATE) && /* @__PURE__ */ T("button", {
|
|
325
|
+
type: "button",
|
|
319
326
|
onClick: () => k(`${A}/${e.id}/edit`),
|
|
320
327
|
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)]",
|
|
321
328
|
children: [/* @__PURE__ */ w(b, {
|
|
@@ -324,6 +331,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
324
331
|
}), O("common.edit", "Edit")]
|
|
325
332
|
}),
|
|
326
333
|
j(d.ORG_DELETE) && /* @__PURE__ */ T("button", {
|
|
334
|
+
type: "button",
|
|
327
335
|
onClick: () => Z(e.id, e.name),
|
|
328
336
|
disabled: K,
|
|
329
337
|
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",
|
|
@@ -394,9 +402,9 @@ function D({ mode: D = "my" } = {}) {
|
|
|
394
402
|
children: [e.logo ? /* @__PURE__ */ w("img", {
|
|
395
403
|
src: e.logo,
|
|
396
404
|
alt: `${e.name} ${O("common.logo", "logo")}`,
|
|
397
|
-
className: "
|
|
405
|
+
className: "size-10 rounded-lg object-cover"
|
|
398
406
|
}) : /* @__PURE__ */ w("div", {
|
|
399
|
-
className: "flex
|
|
407
|
+
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)]",
|
|
400
408
|
children: e.name[0]
|
|
401
409
|
}), /* @__PURE__ */ T("div", {
|
|
402
410
|
className: "min-w-0",
|
|
@@ -428,6 +436,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
428
436
|
className: "flex items-center justify-end gap-2",
|
|
429
437
|
children: [
|
|
430
438
|
/* @__PURE__ */ w("button", {
|
|
439
|
+
type: "button",
|
|
431
440
|
onClick: () => k(`${A}/${e.id}`),
|
|
432
441
|
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)]",
|
|
433
442
|
title: `${O("common.view", "View")} ${e.name}`,
|
|
@@ -438,6 +447,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
438
447
|
})
|
|
439
448
|
}),
|
|
440
449
|
j(d.ORG_UPDATE) && /* @__PURE__ */ w("button", {
|
|
450
|
+
type: "button",
|
|
441
451
|
onClick: () => k(`${A}/${e.id}/edit`),
|
|
442
452
|
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)]",
|
|
443
453
|
title: `${O("common.edit", "Edit")} ${e.name}`,
|
|
@@ -448,6 +458,7 @@ function D({ mode: D = "my" } = {}) {
|
|
|
448
458
|
})
|
|
449
459
|
}),
|
|
450
460
|
j(d.ORG_DELETE) && /* @__PURE__ */ w("button", {
|
|
461
|
+
type: "button",
|
|
451
462
|
onClick: () => Z(e.id, e.name),
|
|
452
463
|
disabled: K,
|
|
453
464
|
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",
|
package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map
CHANGED
|
@@ -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 the legacy `organizations` query (gateway @rbac(scopeType:\n * \"global\")). Restricted to actors with global `organization:read` (super\n * admins / service / custom global-admin roles). Use this in the admin app\n * where operators need the tenant-wide list. Once `allOrganizations` is\n * published through Hive Cloud + codegen, switch to `useAllOrganizations`.\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\">\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 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 >\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 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 >\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 className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\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 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 >\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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <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=\"h-12 w-12 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-12 w-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 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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <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 className=\"hidden overflow-hidden rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)] md:block\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-[var(--color-bg-subtle)]\">\n <tr>\n <th className=\"w-12 px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={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=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-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 className=\"flex items-center justify-end gap-2\">\n <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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <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 the legacy `organizations` query (gateway @rbac(scopeType:\n * \"global\")). Restricted to actors with global `organization:read` (super\n * admins / service / custom global-admin roles). Use this in the admin app\n * where operators need the tenant-wide list. Once `allOrganizations` is\n * published through Hive Cloud + codegen, switch to `useAllOrganizations`.\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\">\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 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 >\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 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 >\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 className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\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 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 >\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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button 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 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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button 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 className=\"hidden overflow-hidden rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] shadow-[var(--shadow-elevation-1)] md:block\">\n <div className=\"overflow-x-auto\">\n <table className=\"min-w-full\">\n <thead className=\"bg-[var(--color-bg-subtle)]\">\n <tr>\n <th className=\"w-12 px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={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 className=\"flex items-center justify-end gap-2\">\n <button 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 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 {hasPermission(PERMISSIONS.ORG_DELETE) && (\n <button 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":";;;;;;;;;;;;;;;;;;;;;;;AAgCA,SAAS,EAAW,GAAc;AAChC,QAAO,IAAI,KAAK,EAAK,CAAC,oBAAoB;;AAqB5C,SAAgB,EAAsB,EAAE,UAAO,SAAqC,EAAE,EAAE;AACtF,GAAwB,qBAAqB;CAC7C,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,OAAuB,GAAgB,EAGnE,IAAgB,GAAsB,sBAAsB,EAC5D,IAAgB,IAAgB,EAAQ,EAAc,QAAS,IAC/D,CAAC,GAAa,KAAkB,EAAS,GAAG,EAC5C,IAAuB,GAAkB,GAAa,IAAI,EAC1D,CAAC,GAAyB,KAA8B,kBAAsB,IAAI,KAAK,CAAC,EACxF,EAAE,sBAAmB,qBAAkB,GAAkB,EAKzD,EAAE,OAAO,MAAwB,GAAa,EAAa,SAAS,GAAG,GAAG,MAAS,KAAK,EAOxF,KAAW,EAAmB,KAAyB,GAAG,MAAS,KAAK,EACxE,KAAc,GAAiB,KAAA,GAAW,KAAyB,GAAG,MAAS,QAAQ,EACvF,EACJ,eAAe,GACf,aACA,UACA,eACE,MAAS,UAAU,KAAc,IAE/B,KAAmB,EAAqB,MAAM,CAAC,SAAS,GAExD,IAAgB,QAAc;EAClC,IAAM,IAAI,EAAqB,MAAM,CAAC,aAAa;AAEnD,SADK,IACE,EAAiB,QACrB,MAAQ,EAAI,MAAM,aAAa,CAAC,SAAS,EAAE,IAAI,EAAI,MAAM,aAAa,CAAC,SAAS,EAAE,CACpF,GAHc;IAId,CAAC,GAAkB,EAAqB,CAAC,EAEtC,EAAE,wBAAoB,SAAS,MAAkB,GAAuB;AAE9E,UAAgB;AACd,KAA4B,MAAsB;GAChD,IAAM,IAAa,IAAI,IAAI,EAAc,KAAK,MAAiB,EAAa,GAAG,CAAC,EAC1E,IAAgB,IAAI,IACxB,CAAC,GAAG,EAAkB,CAAC,QAAQ,MAAmB,EAAW,IAAI,EAAe,CAAC,CAClF;AAMD,UAJI,EAAc,SAAS,EAAkB,OACpC,IAGF;IACP;IACD,CAAC,EAAc,CAAC;CAEnB,IAAM,IAAwB,QACtB,EAAc,QAAQ,MAAiB,EAAwB,IAAI,EAAa,GAAG,CAAC,EAC1F,CAAC,GAAe,EAAwB,CACzC,EAEK,IACJ,EAAc,SAAS,KACvB,EAAc,OAAO,MAAiB,EAAwB,IAAI,EAAa,GAAG,CAAC,EAC/E,IACJ,EAAc,MAAM,MAAiB,EAAwB,IAAI,EAAa,GAAG,CAAC,IAClF,CAAC,GAEG,IAAgB,OAAO,MAA8B;AACzD,MAAI;GAUF,IAAM,KATU,MAAM,QAAQ,WAC5B,EAAgB,IAAI,OAAO,MAAmB;IAC5C,IAAM,IAAS,MAAM,GAAmB,EAAe;AACvD,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,gCAAgC;KAE/E,CACH,EAE8B,MAAM,MAAW,EAAO,WAAW,WAAW;AAC7E,OAAI,GAAgB,WAAW,WAC7B,OAAM,EAAe;AAIvB,GADA,kBAA2B,IAAI,KAAK,CAAC,EACrC,MAAM,GAAS;GACf,IAAM,IACJ,EAAgB,WAAW,IACvB,EAAG,sCAAsC,oCAAoC,GAC7E,GAAG,EAAgB,OAAO,GAAG,EAAG,oCAAoC,qCAAqC;AAG/G,GAFA,EAAM,QAAQ,EAAW,EACpB,EAAa,UAAU,EACxB,GAAU,IAAO,EAAY,EAAW;WACrC,GAAa;GACpB,IAAM,IACJ,aAAuB,QACnB,EAAY,UACZ,EAAG,+BAA+B,gCAAgC;AAGxE,GAFA,EAAM,MAAM,EAAS,EAChB,EAAa,QAAQ,EACtB,GAAU,IAAO,EAAY,EAAS;;IAIxC,KAAgB,GAAwB,MAA6B;AACzE,KAAa,SAAS;EACtB,IAAM,IAAQ,EAAG,8BAA8B,sBAAsB,EAC/D,IAAc,GAAG,EAAG,sCAAsC,SAAS,CAAC,GAAG,EAAiB,IAAI,EAAG,yBAAyB,gCAAgC;AACzJ,KAAc;GACjB;GACA,SAAS;GACT,eAAe,EAAG,iBAAiB,SAAS;GAC7C,CAAC,CAAC,MAAM,MAAiB;AACxB,GAAI,MAAiB,OACnB,EAAkB;IAChB;IACA;IACA,aAAa,EAAG,iBAAiB,SAAS;IAC1C,SAAS;IACT,iBAAiB;AACV,OAAc,CAAC,EAAe,CAAC;;IAEvC,CAAC,GACO,KACJ,EAAc,CAAC,EAAe,CAAC;IAEtC;IAGE,WAAyB;AACzB,IAAsB,WAAW,KAErC,EAAkB;GAChB,OAAO,EAAG,kCAAkC,gCAAgC;GAC5E,aAAa,GAAG,EAAG,mCAAmC,SAAS,CAAC,GAAG,EAAsB,OAAO,GAAG,EACjG,EAAsB,WAAW,IAC7B,0CACA,uCACJ,EAAsB,WAAW,IAAI,2BAA2B,0BACjE,CAAC,GAAG,EAAG,yBAAyB,gCAAgC;GACjE,aAAa,GAAG,EAAG,iBAAiB,SAAS,CAAC,GAAG,EAAsB;GACvE,SAAS;GACT,iBAAiB;AACV,MAAc,EAAsB,KAAK,MAAiB,EAAa,GAAG,CAAC;;GAEnF,CAAC;IAGE,KAA+B,GAAwB,MAAqB;AAChF,KAA4B,MAAsB;GAChD,IAAM,IAAgB,IAAI,IAAI,EAAkB;AAMhD,UALI,IACF,EAAc,IAAI,EAAe,GAEjC,EAAc,OAAO,EAAe,EAE/B;IACP;IAGE,KAA0B,MAAqB;AACnD,KAA4B,MAAsB;GAChD,IAAM,IAAgB,IAAI,IAAI,EAAkB;AAQhD,UAPA,EAAc,SAAS,MAAiB;AACtC,IAAI,IACF,EAAc,IAAI,EAAa,GAAG,GAElC,EAAc,OAAO,EAAa,GAAG;KAEvC,EACK;IACP;;AAyCJ,QAtCI,KAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAUN,KAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,8BAA8B,2BAA2B,EAAI,CAAA;EACnF,CAAA,GAIN,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,IAAD;GACE,SAAS,EAAM;GACf,QAAQ;IACN,OAAO,EAAG,gBAAgB,QAAQ;IAClC,eAAe;AACR,QAAS;;IAEjB;GACD,CAAA;EACE,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,4BAA4B,gBAAgB;OAC7C,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,+BACA,sEACD;OACC,CAAA,CACA;SAEN,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,MAAS,QACR,kBAAC,UAAD;OAAQ,MAAK;OACX,eAAe,EAAS,GAAG,EAAS,UAAU;OAC9C,WAAU;OACV,cAAY,EAAG,oCAAoC,cAAc;iBAHnE;QAKE,kBAAC,IAAD;SAAM,MAAM;SAAI,eAAY;SAAS,CAAA;QACrC,kBAAC,QAAD;SAAM,WAAU;mBACb,EAAG,oCAAoC,cAAc;SACjD,CAAA;QACN,IAAsB,KACrB,kBAAC,QAAD;SACE,WAAU;SACV,cAAY,GAAG,EAAoB;mBAElC;SACI,CAAA;QAEF;UAGV,KAAiB,EAAc,EAAY,WAAW,IACrD,kBAAC,UAAD;OAAQ,MAAK;OACX,eAAe,EAAS,GAAG,EAAS,MAAM;OAC1C,WAAU;iBAFZ,CAIE,kBAAC,IAAD;QAAM,MAAM;QAAI,eAAY;QAAS,CAAA,EACrC,kBAAC,QAAD;QAAM,WAAU;kBACb,EAAG,+BAA+B,sBAAsB;QACpD,CAAA,CACA;SAEP;QACF;;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,IAAD;OACE,OAAO;OACP,UAAU;OACV,aAAa,EAAG,wCAAwC,0BAA0B;OAClF,OAAO,EAAG,kCAAkC,uBAAuB;OACnE,SAAQ;OACR,WAAU;OACV,CAAA,EAEF,kBAAC,UAAD;OAAQ,MAAK;OACX,eAAe;AACR,WAAS;;OAEhB,WAAU;OACV,cAAY,EAAG,8BAA8B,wBAAwB;OACrE,OAAO,EAAG,8BAA8B,wBAAwB;iBAEhE,kBAAC,IAAD;QAAW,MAAM;QAAI,eAAY;QAAS,CAAA;OACnC,CAAA,CACL;SAEL,EAAc,SAAS,IACtB,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QAAO,WAAU;kBAAjB,CACE,kBAAC,GAAD;SACE,SAAS;SACT,eAAe;SACf,UAAU;SACV,WAAW,EAAG,gCAAgC,2BAA2B;SACzE,CAAA,EACF,kBAAC,QAAD,EAAA,UAAO,EAAG,2BAA2B,qBAAqB,EAAQ,CAAA,CAC5D;WAEP,EAAwB,OAAO,IAC9B,kBAAC,UAAD;QAAQ,MAAK;QACX,eAAe,kBAA2B,IAAI,KAAK,CAAC;QACpD,WAAU;kBAET,EAAG,yBAAyB,kBAAkB;QACxC,CAAA,GACP,KACA;UAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAwB;SAAK;SAAE,EAAG,mBAAmB,WAAW;SAC5D;WACN,EAAc,EAAY,WAAW,IACpC,kBAAC,UAAD;QAAQ,MAAK;QACX,SAAS;QACT,UAAU,EAAsB,WAAW,KAAK;QAChD,WAAU;kBAHZ;SAKE,kBAAC,GAAD;UAAQ,MAAM;UAAI,eAAY;UAAS,CAAA;SACvC,kBAAC,QAAD,EAAA,UAAO,EAAG,yBAAyB,kBAAkB,EAAQ,CAAA;SAC5D,EAAsB,SAAS,IAC9B,kBAAC,QAAD,EAAA,UAAA;UAAM;UAAE,EAAsB;UAAO;UAAQ,EAAA,CAAA,GAC3C;SACG;UAEP;SACF;UACJ,KACA;;IAEL,EAAc,WAAW,IACxB,kBAAC,OAAD;KAAK,WAAU;eACZ,MAAoB,EAAiB,SAAS,IAC7C,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD;OAAW,MAAM;OAAI,eAAY;OAAS,CAAA;MAChD,OAAO,EAAG,qCAAqC,4BAA4B;MAC3E,aAAa,EACX,2CACA,yFACD;MACD,QAAQ;OACN,OAAO,EAAG,sBAAsB,eAAe;OAC/C,eAAe,EAAe,GAAG;OAClC;MACD,CAAA,GAEF,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD;OAAW,MAAM;OAAI,eAAY;OAAS,CAAA;MAChD,OAAO,EAAG,iCAAiC,yBAAyB;MACpE,aAAa,EACX,uCACA,qFACD;MACD,QACE,KAAiB,EAAc,EAAY,WAAW,GAClD;OACE,OAAO,EAAG,+BAA+B,sBAAsB;OAC/D,eAAe,EAAS,GAAG,EAAS,MAAM;OAC3C,GACD,KAAA;MAEN,CAAA;KAEA,CAAA,GAEN,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAc,KAAK,MAClB,kBAAC,WAAD;MAEE,WAAU;gBAFZ,CAIE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,SAAS,EAAwB,IAAI,EAAa,GAAG;QACrD,WAAW,MAAY,EAA4B,EAAa,IAAI,EAAQ;QAC5E,WAAW,GAAG,EAAG,gCAAgC,sBAAsB,CAAC,GAAG,EAAa;QACxF,CAAA,EAEF,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACG,EAAa,OACZ,kBAAC,OAAD;SACE,KAAK,EAAa;SAClB,KAAK,GAAG,EAAa,KAAK,GAAG,EAAG,eAAe,OAAO;SACtD,WAAU;SACV,CAAA,GAEF,kBAAC,OAAD;SAAK,WAAU;mBACZ,EAAa,KAAK;SACf,CAAA,EAGR,kBAAC,OAAD;SAAK,WAAU;mBAAf;UACE,kBAAC,OAAD;WAAK,WAAU;qBAAf,CACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAa;YACX,CAAA,EACL,kBAAC,GAAD,EAAyB,QAAQ,EAAa,QAAU,CAAA,CACpD;;UACN,kBAAC,KAAD;WAAG,WAAU;qBAAb,CAA+E,KAC3E,EAAa,KACb;;UACJ,kBAAC,MAAD;WAAI,WAAU;qBAAd,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAG,wCAAwC,aAAa;YACtD,CAAA,EACL,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAa,kBAAkB;YAC7B,CAAA,CACD,EAAA,CAAA,EACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAG,kBAAkB,UAAU;YAC7B,CAAA,EACL,kBAAC,MAAD;YAAI,WAAU;sBACX,EAAW,EAAa,UAAU;YAChC,CAAA,CACD,EAAA,CAAA,CACH;;UACD;WACF;UACF;UAEN,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,UAAD;SAAQ,MAAK;SACX,eAAe,EAAS,GAAG,EAAS,GAAG,EAAa,KAAK;SACzD,WAAU;mBAFZ,CAIE,kBAAC,GAAD;UAAK,MAAM;UAAI,eAAY;UAAS,CAAA,EACnC,EAAG,eAAe,OAAO,CACnB;;QACR,EAAc,EAAY,WAAW,IACpC,kBAAC,UAAD;SAAQ,MAAK;SACX,eAAe,EAAS,GAAG,EAAS,GAAG,EAAa,GAAG,OAAO;SAC9D,WAAU;mBAFZ,CAIE,kBAAC,GAAD;UAAW,MAAM;UAAI,eAAY;UAAS,CAAA,EACzC,EAAG,eAAe,OAAO,CACnB;;QAEV,EAAc,EAAY,WAAW,IACpC,kBAAC,UAAD;SAAQ,MAAK;SACX,eAAe,EAAa,EAAa,IAAI,EAAa,KAAK;SAC/D,UAAU;SACV,WAAU;mBAHZ,CAKE,kBAAC,GAAD;UAAQ,MAAM;UAAI,eAAY;UAAS,CAAA,EACtC,EAAG,iBAAiB,SAAS,CACvB;;QAEP;SACE;QAnFH,EAAa,GAmFV,CACV;KACE,CAAA,EAEN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,SAAD;OAAO,WAAU;iBAAjB,CACE,kBAAC,SAAD;QAAO,WAAU;kBACf,kBAAC,MAAD,EAAA,UAAA;SACE,kBAAC,MAAD;UAAI,WAAU;oBACZ,kBAAC,GAAD;WACE,SAAS;WACT,eAAe;WACf,UAAU;WACV,WAAW,EAAG,gCAAgC,2BAA2B;WACzE,CAAA;UACC,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,0CAA0C,eAAe;UAC1D,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,iBAAiB,SAAS;UAC3B,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,wCAAwC,aAAa;UACtD,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,kBAAkB,UAAU;UAC7B,CAAA;SACL,kBAAC,MAAD;UAAI,WAAU;oBACX,EAAG,kBAAkB,UAAU;UAC7B,CAAA;SACF,EAAA,CAAA;QACC,CAAA,EACR,kBAAC,SAAD;QAAO,WAAU;kBACd,EAAc,KAAK,MAClB,kBAAC,MAAD;SAEE,WAAU;mBAFZ;UAIE,kBAAC,MAAD;WAAI,WAAU;qBACZ,kBAAC,GAAD;YACE,SAAS,EAAwB,IAAI,EAAa,GAAG;YACrD,WAAW,MACT,EAA4B,EAAa,IAAI,EAAQ;YAEvD,WAAW,GAAG,EAAG,gCAAgC,sBAAsB,CAAC,GAAG,EAAa;YACxF,CAAA;WACC,CAAA;UACL,kBAAC,MAAD;WAAI,WAAU;qBACZ,kBAAC,OAAD;YAAK,WAAU;sBAAf,CACG,EAAa,OACZ,kBAAC,OAAD;aACE,KAAK,EAAa;aAClB,KAAK,GAAG,EAAa,KAAK,GAAG,EAAG,eAAe,OAAO;aACtD,WAAU;aACV,CAAA,GAEF,kBAAC,OAAD;aAAK,WAAU;uBACZ,EAAa,KAAK;aACf,CAAA,EAER,kBAAC,OAAD;aAAK,WAAU;uBAAf,CACE,kBAAC,KAAD;cAAG,WAAU;wBACV,EAAa;cACZ,CAAA,EACJ,kBAAC,KAAD;cAAG,WAAU;wBAAb,CAA0E,KACtE,EAAa,KACb;gBACA;eACF;;WACH,CAAA;UACL,kBAAC,MAAD;WAAI,WAAU;qBACZ,kBAAC,GAAD,EAAyB,QAAQ,EAAa,QAAU,CAAA;WACrD,CAAA;UACL,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAa,kBAAkB;WAC7B,CAAA;UACL,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAW,EAAa,UAAU;WAChC,CAAA;UACL,kBAAC,MAAD;WAAI,WAAU;qBACZ,kBAAC,OAAD;YAAK,WAAU;sBAAf;aACE,kBAAC,UAAD;cAAQ,MAAK;cACX,eAAe,EAAS,GAAG,EAAS,GAAG,EAAa,KAAK;cACzD,WAAU;cACV,OAAO,GAAG,EAAG,eAAe,OAAO,CAAC,GAAG,EAAa;cACpD,cAAY,GAAG,EAAG,eAAe,OAAO,CAAC,GAAG,EAAa;wBAEzD,kBAAC,GAAD;eAAK,MAAM;eAAI,eAAY;eAAS,CAAA;cAC7B,CAAA;aACR,EAAc,EAAY,WAAW,IACpC,kBAAC,UAAD;cAAQ,MAAK;cACX,eAAe,EAAS,GAAG,EAAS,GAAG,EAAa,GAAG,OAAO;cAC9D,WAAU;cACV,OAAO,GAAG,EAAG,eAAe,OAAO,CAAC,GAAG,EAAa;cACpD,cAAY,GAAG,EAAG,eAAe,OAAO,CAAC,GAAG,EAAa;wBAEzD,kBAAC,GAAD;eAAW,MAAM;eAAI,eAAY;eAAS,CAAA;cACnC,CAAA;aAEV,EAAc,EAAY,WAAW,IACpC,kBAAC,UAAD;cAAQ,MAAK;cACX,eAAe,EAAa,EAAa,IAAI,EAAa,KAAK;cAC/D,UAAU;cACV,WAAU;cACV,OAAO,GAAG,EAAG,iBAAiB,SAAS,CAAC,GAAG,EAAa;cACxD,cAAY,GAAG,EAAG,iBAAiB,SAAS,CAAC,GAAG,EAAa;wBAE7D,kBAAC,GAAD;eAAQ,MAAM;eAAI,eAAY;eAAS,CAAA;cAChC,CAAA;aAEP;;WACH,CAAA;UACF;WA7EE,EAAa,GA6Ef,CACL;QACI,CAAA,CACF;;MACJ,CAAA;KACF,CAAA,CACL,EAAA,CAAA;IAED;MAEL,EACG"}
|
package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n organizationId,\n 100,\n 0,\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\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)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\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)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AddWorkspaceMemberDialog.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { AlertCircle, Loader2, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from '@burdenoff/fe-libs/ui';\nimport { useMembers } from '../../members/hooks/useMembers';\nimport { useAddWorkspaceMember } from '../hooks/useWorkspaceMembers';\nimport type { WorkspaceMember } from '../types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface AddWorkspaceMemberDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n workspaceId: string;\n organizationId: string;\n existingMembers: WorkspaceMember[];\n onInviteOrganizationMember?: () => void;\n}\n\nexport function AddWorkspaceMemberDialog({\n open,\n onOpenChange,\n workspaceId,\n organizationId,\n existingMembers,\n onInviteOrganizationMember,\n}: AddWorkspaceMemberDialogProps) {\n const tr = useTr();\n const [selectedUserId, setSelectedUserId] = useState('');\n const [selectedRole, setSelectedRole] = useState<'MEMBER' | 'WORKSPACE_ADMIN'>('MEMBER');\n const [submissionError, setSubmissionError] = useState<string | null>(null);\n\n const { members: organizationMembers, loading: membersLoading } = useMembers(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId,\n 100,\n 0,\n // react-doctor-disable-next-line react-doctor/no-event-handler\n open\n );\n const { addWorkspaceMember, loading: addingMember } = useAddWorkspaceMember();\n\n const existingUserIds = useMemo(\n // react-doctor-disable-next-line react-doctor/no-event-handler\n () => new Set(existingMembers.map((member) => member.userId)),\n [existingMembers]\n );\n\n const availableMembers = useMemo(\n () => organizationMembers.filter((member) => !existingUserIds.has(member.userId)),\n [existingUserIds, organizationMembers]\n );\n\n useEffect(() => {\n if (!open) {\n setSelectedUserId('');\n setSelectedRole('MEMBER');\n setSubmissionError(null);\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!selectedUserId && availableMembers.length > 0) {\n setSelectedUserId(availableMembers[0]?.userId ?? '');\n }\n }, [availableMembers, open, selectedUserId]);\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!selectedUserId) {\n setSubmissionError(\n tr(\n 'workspaces.addMember.selectRequired',\n 'Select an organization member to add to this workspace.'\n )\n );\n return;\n }\n\n try {\n setSubmissionError(null);\n const result = await addWorkspaceMember({\n workspaceId,\n userId: selectedUserId,\n role: selectedRole,\n });\n\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n\n toast.success(tr('workspaces.addMember.success', 'Workspace member added successfully'));\n onOpenChange(false);\n } catch (error) {\n setSubmissionError(\n error instanceof Error\n ? error.message\n : tr('workspaces.addMember.failed', 'Failed to add workspace member')\n );\n }\n };\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-[520px] border-[var(--color-border-default)] bg-[var(--color-bg-surface)] text-[var(--color-text-primary)]\">\n <DialogHeader>\n <DialogTitle className=\"flex items-center gap-2 text-[var(--color-text-primary)]\">\n <UserPlus size={18} aria-hidden=\"true\" />\n {tr('workspaces.addMember.title', 'Add workspace member')}\n </DialogTitle>\n <DialogDescription className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.addMember.description',\n 'Choose an existing organization member and assign their workspace role.'\n )}\n </DialogDescription>\n </DialogHeader>\n\n <form onSubmit={handleSubmit} className=\"space-y-4\">\n {submissionError ? (\n <div className=\"flex items-start gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3 text-sm text-[var(--color-status-error-text)]\">\n <AlertCircle size={16} className=\"mt-0.5 shrink-0\" aria-hidden=\"true\" />\n <span>{submissionError}</span>\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"workspace-member-user\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.memberLabel', 'Organization member')}\n </label>\n <select\n id=\"workspace-member-user\"\n value={selectedUserId}\n onChange={(event) => setSelectedUserId(event.target.value)}\n disabled={membersLoading || addingMember || availableMembers.length === 0}\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)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n {availableMembers.length === 0 ? (\n <option value=\"\">\n {tr(\n 'workspaces.addMember.noneAvailable',\n 'No eligible organization members available'\n )}\n </option>\n ) : null}\n {availableMembers.map((member) => (\n <option key={member.id} value={member.userId}>\n {member.userId} ({member.role})\n </option>\n ))}\n </select>\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.addMember.filterHelp',\n 'Only members that belong to this organization and are not already in the workspace are listed.'\n )}\n </p>\n {availableMembers.length === 0 && onInviteOrganizationMember ? (\n <button\n type=\"button\"\n onClick={() => {\n onOpenChange(false);\n onInviteOrganizationMember();\n }}\n className=\"mt-2 inline-flex items-center gap-2 text-sm font-medium text-[var(--color-action-primary-bg)] hover:underline\"\n >\n {tr(\n 'workspaces.addMember.inviteInstead',\n 'Invite a new organization member instead'\n )}\n </button>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"workspace-member-role\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.addMember.roleLabel', 'Workspace role')}\n </label>\n <select\n id=\"workspace-member-role\"\n value={selectedRole}\n onChange={(event) =>\n setSelectedRole(event.target.value as 'MEMBER' | 'WORKSPACE_ADMIN')\n }\n disabled={addingMember}\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)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n >\n <option value=\"MEMBER\">{tr('members.role.member', 'Member')}</option>\n <option value=\"WORKSPACE_ADMIN\">\n {tr('members.role.workspaceAdmin', 'Workspace Admin')}\n </option>\n </select>\n </div>\n\n <DialogFooter className=\"gap-3 sm:gap-0\">\n <button\n type=\"button\"\n onClick={() => onOpenChange(false)}\n className=\"rounded-lg border border-[var(--color-border-default)] px-4 py-2 text-[var(--color-text-primary)] transition-colors hover:bg-[var(--color-bg-subtle)]\"\n disabled={addingMember}\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n <button\n type=\"submit\"\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={addingMember || membersLoading || availableMembers.length === 0}\n >\n {addingMember ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {tr('workspaces.addMember.submit', 'Add member')}\n </button>\n </DialogFooter>\n </form>\n </DialogContent>\n </Dialog>\n );\n}\n"],"mappings":"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"}
|