@burdenoff/microfe-organization 2026.616.1 → 2026.624.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/organization-management/modules/members/components/BulkInviteForm.js +2 -2
- package/dist/organization-management/modules/members/components/BulkInviteForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -2
- package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -2
- package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MembersListPage.js +2 -2
- package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js +2 -2
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +5 -5
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js +2 -2
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +3 -3
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +3 -3
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +3 -3
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +2 -2
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +3 -3
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +2 -2
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -1
- package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js +1 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
- package/dist/organization-management/shared/components/input/SearchInput.js +1 -1
- package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || workspace?.tenantId || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between. With a single shared tenant there is nothing to\n // choose, so the picker is hidden and the tenant is auto-selected.\n const showTenantPicker = shouldLoadTenants && tenants.length > 1;\n\n // Auto-select the shared (or only) tenant when there is no explicit tenant\n // and no real choice to make, so the workspace lands in the shared tenant\n // without surfacing a picker.\n useEffect(() => {\n if (!shouldLoadTenants || formData.tenantId || tenants.length === 0) {\n return;\n }\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const resolved = sharedTenant?.id ?? tenants[0]?.id;\n if (resolved) {\n setFormData((previousState) =>\n previousState.tenantId ? previousState : { ...previousState, tenantId: resolved }\n );\n }\n }, [shouldLoadTenants, tenants, formData.tenantId]);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n {/* Tenant picker — shown ONLY when the user has more than one tenant\n to choose between. For single-tenant (shared) users the tenant is\n auto-selected and no picker is rendered. */}\n {showTenantPicker ? (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n aria-label={tr('common.organization', 'Organization')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n aria-label={tr('workspaces.form.name', 'Workspace Name')}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n ) : null}\n </div>\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 hover:scale-[1.02] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceForm.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/components/WorkspaceForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { Building, Home, Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput, Workspace } from '../types';\nimport { createWorkspaceSchema, updateWorkspaceSchema } from '../schemas';\nimport { useOrganizations } from '../../organizations/hooks/useOrganizations';\nimport { useOrgsWithPermission } from '../../organizations/hooks/useOrgsWithPermission';\nimport { useTenants } from '../hooks/useWorkspaces';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface WorkspaceFormProps {\n workspace?: Workspace | null;\n tenantId?: string;\n tenantName?: string;\n organizationId?: string;\n organizationName?: string;\n showOrganizationSelector?: boolean;\n onSubmit: (data: CreateWorkspaceInput | UpdateWorkspaceInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\nexport function WorkspaceForm({\n workspace,\n tenantId,\n organizationId,\n organizationName,\n showOrganizationSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: WorkspaceFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: workspace?.name || '',\n slug: workspace?.slug || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || workspace?.tenantId || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n organizationId: organizationId || workspace?.organizationId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n\n const shouldLoadTenants = !workspace && showOrganizationSelector && !tenantId;\n const shouldLoadOrganizations = !workspace && showOrganizationSelector && !organizationId;\n\n const { tenants, loading: tenantsLoading } = useTenants(100, 0, shouldLoadTenants);\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between. With a single shared tenant there is nothing to\n // choose, so the picker is hidden and the tenant is auto-selected.\n const showTenantPicker = shouldLoadTenants && tenants.length > 1;\n\n // Auto-select the shared (or only) tenant when there is no explicit tenant\n // and no real choice to make, so the workspace lands in the shared tenant\n // without surfacing a picker.\n useEffect(() => {\n if (!shouldLoadTenants || formData.tenantId || tenants.length === 0) {\n return;\n }\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const resolved = sharedTenant?.id ?? tenants[0]?.id;\n if (resolved) {\n setFormData((previousState) =>\n previousState.tenantId ? previousState : { ...previousState, tenantId: resolved }\n );\n }\n }, [shouldLoadTenants, tenants, formData.tenantId]);\n const { organizations, loading: organizationsLoading } = useOrganizations(\n formData.tenantId ? { tenantId: formData.tenantId } : undefined,\n 100,\n 0,\n shouldLoadOrganizations\n );\n\n // Hide orgs where the user can't actually create a workspace, so the\n // dropdown only surfaces options the gateway @rbac(workspace:create) will\n // accept. Membership alone isn't enough — a Viewer of an org is a member\n // but has workspace=VIEWER (no create). The bulk checkPermissions call is\n // one round-trip regardless of how many orgs are on the list.\n const allOrgIds = useMemo(\n () => organizations.map((organization) => organization.id),\n [organizations]\n );\n const { allowedIds: workspaceCreateAllowedOrgIds, loading: permissionCheckLoading } =\n useOrgsWithPermission(allOrgIds, 'workspace', 'create');\n const filterByPermission = !workspace && showOrganizationSelector && !organizationId;\n const permittedOrganizations = useMemo(\n () =>\n filterByPermission\n ? organizations.filter((organization) => workspaceCreateAllowedOrgIds.has(organization.id))\n : organizations,\n [organizations, workspaceCreateAllowedOrgIds, filterByPermission]\n );\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (tenantId && tenantId !== formData.tenantId) {\n setFormData((previousState) => ({ ...previousState, tenantId }));\n }\n }, [formData.tenantId, tenantId]);\n\n useEffect(() => {\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (organizationId && organizationId !== formData.organizationId) {\n setFormData((previousState) => ({ ...previousState, organizationId }));\n }\n }, [formData.organizationId, organizationId]);\n\n useEffect(() => {\n if (!showOrganizationSelector || organizationId || workspace) {\n return;\n }\n\n setFormData((previousState) => {\n if (!previousState.organizationId) {\n return previousState;\n }\n\n const hasMatchingOrganization = permittedOrganizations.some(\n (organization) => organization.id === previousState.organizationId\n );\n\n if (hasMatchingOrganization) {\n return previousState;\n }\n\n return { ...previousState, organizationId: '' };\n });\n }, [organizationId, permittedOrganizations, showOrganizationSelector, workspace]);\n\n const selectedOrganizationLabel = useMemo(() => {\n if (organizationName) return organizationName;\n\n const selectedOrganization = permittedOrganizations.find(\n (organization) => organization.id === formData.organizationId\n );\n return selectedOrganization?.name ?? formData.organizationId;\n }, [formData.organizationId, organizationName, permittedOrganizations]);\n\n const validate = () => {\n try {\n const schema = workspace ? updateWorkspaceSchema : createWorkspaceSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (validationError) {\n if (validationError instanceof ZodError) {\n const nextErrors: Record<string, string> = {};\n validationError.issues.forEach((issue) => {\n const path = issue.path.join('.');\n nextErrors[path] = issue.message;\n });\n setErrors(nextErrors);\n }\n\n return false;\n }\n };\n\n const handleSubmit = async (event: React.FormEvent) => {\n event.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = workspace\n ? {\n name: formData.name !== workspace.name ? formData.name : undefined,\n }\n : {\n tenantId: formData.tenantId,\n organizationId: formData.organizationId,\n name: formData.name,\n slug: formData.slug,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {!workspace ? (\n <>\n {/* Tenant picker — shown ONLY when the user has more than one tenant\n to choose between. For single-tenant (shared) users the tenant is\n auto-selected and no picker is rendered. */}\n {showTenantPicker ? (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n <div className=\"relative\">\n <select\n id=\"tenantId\"\n value={formData.tenantId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n tenantId: event.target.value,\n organizationId: '',\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={loading || tenantsLoading}\n required\n >\n <option value=\"\">\n {tr('workspaces.form.selectTenant', 'Select a tenant...')}\n </option>\n {tenants.map((tenant) => (\n <option key={tenant.id} value={tenant.id}>\n {tenant.name} ({tenant.slug})\n </option>\n ))}\n </select>\n <Home\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {tenantsLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n {errors.tenantId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.tenantId}\n </p>\n ) : null}\n </div>\n ) : null}\n\n <div>\n <label\n htmlFor=\"organizationId\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.organization', 'Organization')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n\n {showOrganizationSelector && !organizationId ? (\n <div className=\"relative\">\n <select\n id=\"organizationId\"\n value={formData.organizationId}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full appearance-none rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-10 text-[var(--color-text-primary)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n disabled={\n loading || organizationsLoading || permissionCheckLoading || !formData.tenantId\n }\n required\n >\n <option value=\"\">\n {!formData.tenantId\n ? tr('workspaces.form.selectTenantFirst', 'Select a tenant first')\n : permittedOrganizations.length === 0 &&\n !organizationsLoading &&\n !permissionCheckLoading\n ? tr(\n 'workspaces.form.noOrganizationsAvailable',\n 'No organizations available — you need workspace create permission'\n )\n : tr('workspaces.form.selectOrganization', 'Select an organization...')}\n </option>\n {permittedOrganizations.map((organization) => (\n <option key={organization.id} value={organization.id}>\n {organization.name} ({organization.slug})\n </option>\n ))}\n </select>\n <Building\n size={18}\n className=\"pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n <div className=\"pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\">\n <svg className=\"size-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M19 9l-7 7-7-7\"\n />\n </svg>\n </div>\n {organizationsLoading || permissionCheckLoading ? (\n <div className=\"absolute right-10 top-1/2 -translate-y-1/2\">\n <Loader2 size={16} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n ) : null}\n </div>\n ) : (\n <div className=\"relative\">\n <input\n id=\"organizationId\"\n type=\"text\"\n value={selectedOrganizationLabel}\n aria-label={tr('common.organization', 'Organization')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n organizationId: event.target.value,\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] py-2 pl-10 pr-4 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.orgIdPlaceholder', 'Enter organization ID')}\n disabled={loading || Boolean(organizationId)}\n required\n autoComplete=\"off\"\n />\n <Building\n size={18}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-[var(--color-text-muted)]\"\n />\n </div>\n )}\n\n {(organizationId || formData.organizationId) && (\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'workspaces.form.creatingUnderOrg',\n 'Creating workspace under the selected organization.'\n )}\n </p>\n )}\n {errors.organizationId ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">\n {errors.organizationId}\n </p>\n ) : null}\n </div>\n </>\n ) : null}\n\n <div>\n <label\n htmlFor=\"name\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('workspaces.form.name', 'Workspace Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n aria-label={tr('workspaces.form.name', 'Workspace Name')}\n onChange={(event) =>\n setFormData((previousState) => ({ ...previousState, name: event.target.value }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.namePlaceholder', 'Enter workspace name')}\n disabled={loading}\n required\n autoComplete=\"organization-title\"\n />\n {errors.name ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n ) : null}\n </div>\n\n <div>\n <label\n htmlFor=\"slug\"\n className=\"mb-1 block text-sm font-medium text-[var(--color-text-primary)]\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(event) =>\n setFormData((previousState) => ({\n ...previousState,\n slug: event.target.value.toLowerCase().replace(/\\s+/g, '-'),\n }))\n }\n className=\"w-full rounded-lg border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] px-3 py-2 text-[var(--color-text-primary)] placeholder:text-[var(--color-text-muted)] focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)] disabled:cursor-not-allowed disabled:bg-[var(--color-bg-muted)]\"\n placeholder={tr('workspaces.form.slugPlaceholder', 'workspace-slug')}\n disabled={loading || Boolean(workspace)}\n required={!workspace}\n autoComplete=\"off\"\n />\n <p className=\"mt-1 text-xs text-[var(--color-text-muted)]\">\n {tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug ? (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n ) : null}\n </div>\n\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[220px]\"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : null}\n {workspace\n ? tr('workspaces.form.updateSubmit', 'Update Workspace')\n : tr('workspaces.create.button', 'Create Workspace')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] disabled:cursor-not-allowed disabled:opacity-50 sm:min-w-[140px]\"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
|
|
@@ -37,7 +37,7 @@ function f() {
|
|
|
37
37
|
/* @__PURE__ */ u("button", {
|
|
38
38
|
type: "button",
|
|
39
39
|
onClick: S,
|
|
40
|
-
className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n
|
|
40
|
+
className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n ",
|
|
41
41
|
children: [/* @__PURE__ */ l(s, { size: 20 }), /* @__PURE__ */ l("span", {
|
|
42
42
|
className: "text-sm font-medium",
|
|
43
43
|
children: y ? `${f("common.backTo", "Back to")} ${b?.name || f("common.organization", "Organization")}` : f("workspaces.create.backToWorkspaces", "Back to Workspaces")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n <div className=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n
|
|
1
|
+
{"version":3,"file":"WorkspaceCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { useSearchParams } from 'react-router-dom';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateWorkspace } from '../hooks/useWorkspaces';\nimport { useOrganization } from '../../organizations/hooks/useOrganizations';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateWorkspaceInput, UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceCreatePage() {\n const tr = useTr();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n // `usePermissions` is still used for the permissionsLoading gate below\n // even though the static workspace:create page-level guard was removed\n // (see comment further down) — keep the hook call so the loading\n // skeleton still waits for the provider to settle before mounting the\n // form.\n const { isLoading: permissionsLoading } = usePermissions();\n const { createWorkspace, loading } = useCreateWorkspace();\n const [searchParams] = useSearchParams();\n\n // Get organizationId from URL query params (e.g., ?organizationId=xxx)\n const organizationIdFromUrl = searchParams.get('organizationId') || undefined;\n\n // Fetch organization details if ID is provided (to show org name and get tenantId)\n const { organization } = useOrganization(organizationIdFromUrl || '');\n\n const handleSubmit = async (data: CreateWorkspaceInput | UpdateWorkspaceInput) => {\n try {\n const createData = data as CreateWorkspaceInput;\n const result = await createWorkspace(createData);\n if (result.data?.createWorkspace) {\n toast.success(tr('workspaces.create.success', 'Workspace created successfully'));\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces/${result.data.createWorkspace.id}`);\n }\n } else if (result.errors && result.errors.length > 0) {\n // Handle GraphQL errors returned in result\n const errorMessage =\n result.errors[0]?.message || tr('workspaces.create.failed', 'Failed to create workspace');\n const errorCode = result.errors[0]?.extensions?.code;\n\n if (errorCode === 'UNAUTHENTICATED') {\n toast.error(tr('common.authRequired', 'Authentication required. Please log in again.'));\n } else {\n toast.error(errorMessage);\n }\n }\n } catch (error) {\n const errorMessage =\n error instanceof Error\n ? error.message\n : tr('common.unexpectedError', 'An unexpected error occurred');\n toast.error(errorMessage);\n }\n };\n\n const handleCancel = () => {\n // Navigate back to organization if came from there, otherwise to workspaces list\n if (organizationIdFromUrl) {\n navigate(`${basePath}/${organizationIdFromUrl}`);\n } else {\n navigate(`${basePath}/workspaces`);\n }\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n // NOTE: deliberately NOT gating on hasPermission(WORKSPACE_CREATE) at the\n // page level. `usePermissions()` here resolves at the outer tenant scope\n // (from OrgConsoleProvider), and `workspace:create` is an org-scope\n // permission — a user who is Owner of even one org would be incorrectly\n // denied entry to the form. WorkspaceForm's organization dropdown is\n // permission-filtered (only orgs where the user actually has\n // workspace:create are listed), and the backend @rbac directive on the\n // createWorkspace mutation is the authoritative enforcement point. If a\n // user has no orgs they can create a workspace in, the dropdown will\n // surface that message and submit will stay disabled.\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n <div className=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {organizationIdFromUrl\n ? `${tr('common.backTo', 'Back to')} ${organization?.name || tr('common.organization', 'Organization')}`\n : tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.create.title', 'Create Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {organizationIdFromUrl && organization\n ? `${tr('workspaces.create.subtitleForOrg', 'Add a new workspace to')} ${organization.name}`\n : tr('workspaces.create.subtitle', 'Add a new workspace to an organization')}\n </p>\n </div>\n\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <WorkspaceForm\n tenantId={organization?.tenantId}\n tenantName={organization?.tenantId}\n organizationId={organizationIdFromUrl}\n organizationName={organization?.name}\n showOrganizationSelector={!organizationIdFromUrl}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAYA,SAAgB,IAAsB;CACpC,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAM3D,EAAE,WAAW,MAAuB,GAAgB,EACpD,EAAE,oBAAiB,eAAY,GAAoB,EACnD,CAAC,KAAgB,GAAiB,EAGlC,IAAwB,EAAa,IAAI,iBAAiB,IAAI,KAAA,GAG9D,EAAE,oBAAiB,EAAgB,KAAyB,GAAG,EAE/D,IAAe,OAAO,MAAsD;AAChF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EAC6B;AAChD,OAAI,EAAO,MAAM,gBAEf,CADA,EAAM,QAAQ,EAAG,6BAA6B,iCAAiC,CAAC,EAE9E,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,cAAc,EAAO,KAAK,gBAAgB,KAAK;YAE7D,EAAO,UAAU,EAAO,OAAO,SAAS,GAAG;IAEpD,IAAM,IACJ,EAAO,OAAO,IAAI,WAAW,EAAG,4BAA4B,6BAA6B;AAG3F,IAFkB,EAAO,OAAO,IAAI,YAAY,SAE9B,oBAChB,EAAM,MAAM,EAAG,uBAAuB,gDAAgD,CAAC,GAEvF,EAAM,MAAM,EAAa;;WAGtB,GAAO;GACd,IAAM,IACJ,aAAiB,QACb,EAAM,UACN,EAAG,0BAA0B,+BAA+B;AAClE,KAAM,MAAM,EAAa;;IAIvB,UAAqB;AAEzB,EACE,EADE,IACO,GAAG,EAAS,GAAG,MAEf,GAAG,EAAS,aAAa;;AAuBtC,QAnBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAgBR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,IACG,GAAG,EAAG,iBAAiB,UAAU,CAAC,GAAG,GAAc,QAAQ,EAAG,uBAAuB,eAAe,KACpG,EAAG,sCAAsC,qBAAqB;MAC7D,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,2BAA2B,mBAAmB;KAC/C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,KAAyB,IACtB,GAAG,EAAG,oCAAoC,yBAAyB,CAAC,GAAG,EAAa,SACpF,EAAG,8BAA8B,yCAAyC;KAC5E,CAAA;IACA;MAEN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,GAAc;KACxB,YAAY,GAAc;KAC1B,gBAAgB;KAChB,kBAAkB,GAAc;KAChC,0BAA0B,CAAC;KAC3B,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF"}
|
|
@@ -86,7 +86,7 @@ function O() {
|
|
|
86
86
|
children: [/* @__PURE__ */ S("button", {
|
|
87
87
|
type: "button",
|
|
88
88
|
onClick: () => A(`${j}/workspaces`),
|
|
89
|
-
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)]
|
|
89
|
+
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)]",
|
|
90
90
|
children: [/* @__PURE__ */ x(m, {
|
|
91
91
|
size: 20,
|
|
92
92
|
"aria-hidden": "true"
|
|
@@ -130,7 +130,7 @@ function O() {
|
|
|
130
130
|
}), P(s.WORKSPACE_UPDATE) && /* @__PURE__ */ S("button", {
|
|
131
131
|
type: "button",
|
|
132
132
|
onClick: () => A(`${j}/workspaces/${k}/edit`),
|
|
133
|
-
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
|
|
133
|
+
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",
|
|
134
134
|
children: [/* @__PURE__ */ x(ne, {
|
|
135
135
|
size: 16,
|
|
136
136
|
"aria-hidden": "true"
|
|
@@ -262,7 +262,7 @@ function O() {
|
|
|
262
262
|
})] }), P(s.MEMBER_INVITE) && /* @__PURE__ */ S("button", {
|
|
263
263
|
type: "button",
|
|
264
264
|
onClick: () => H(!0),
|
|
265
|
-
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
|
|
265
|
+
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",
|
|
266
266
|
children: [/* @__PURE__ */ x(_, {
|
|
267
267
|
size: 16,
|
|
268
268
|
"aria-hidden": "true"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Briefcase, Edit, Trash2, UserPlus, Users } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n ActorIdentity,\n buildActorFallback,\n useActorProfiles,\n type ActorInput,\n} from '@burdenoff/fe-libs/shared';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useRemoveWorkspaceMember, useWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { WorkspaceStatusBadge } from '../components/WorkspaceStatusBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { MemberRoleBadge } from '../../members/components/MemberRoleBadge';\nimport { AddWorkspaceMemberDialog } from '../components/AddWorkspaceMemberDialog';\nimport { useConfirmDialog } from '../../../shared/components/dialog/ConfirmDialog';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype ActiveTab = 'overview' | 'members';\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 WorkspaceDetailPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n const { navigate, basePath = '/organizations', apiGatewayUrl, authToken } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { workspace, loading, error } = useWorkspace(id ?? '');\n const [activeTab, setActiveTab] = useState<ActiveTab>('overview');\n const [isAddMemberDialogOpen, setIsAddMemberDialogOpen] = useState(false);\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n const shouldFetchMembers = activeTab === 'members' && Boolean(id);\n const {\n members,\n loading: membersLoading,\n error: membersError,\n refetch: refetchMembers,\n } = useWorkspaceMembers(shouldFetchMembers ? (id ?? '') : '', 50, 0);\n const { removeWorkspaceMember, loading: removingMember } = useRemoveWorkspaceMember();\n const resolvedMemberCount =\n members.length > 0 || activeTab === 'members' ? members.length : (workspace?.memberCount ?? 0);\n\n const memberActors = useMemo<ActorInput[]>(\n () => members.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [members]\n );\n const profileMap = useActorProfiles(memberActors, {\n apiGatewayUrl,\n authToken,\n orgId: workspace?.organizationId,\n workspaceId: id,\n });\n\n const tabItems = useMemo(\n () => [\n { id: 'overview' as const, label: tr('workspaces.detail.tab.overview', 'Overview') },\n {\n id: 'members' as const,\n label: `${tr('workspaces.detail.tab.members', 'Members')} (${resolvedMemberCount})`,\n },\n ],\n [resolvedMemberCount, tr]\n );\n\n const handleRemoveMember = (userId: string) => {\n if (!id) return;\n\n openConfirmDialog({\n title: tr('workspaces.removeMember.title', 'Remove workspace member'),\n description: `${tr('workspaces.removeMember.prefix', 'Remove')} ${userId} ${tr('workspaces.removeMember.suffix', 'from this workspace? They will keep their organization membership.')}`,\n confirmText: tr('common.remove', 'Remove'),\n variant: 'danger',\n onConfirm: () => {\n void (async () => {\n try {\n const result = await removeWorkspaceMember(id, userId);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.removeMember.failed', 'Failed to remove workspace member')\n );\n }\n\n await refetchMembers();\n toast.success(\n tr('workspaces.removeMember.success', 'Workspace member removed successfully')\n );\n } catch (removeError) {\n toast.error(\n removeError instanceof Error\n ? removeError.message\n : tr('workspaces.removeMember.failed', 'Failed to remove workspace member')\n );\n }\n })();\n },\n });\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.WORKSPACE_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 'workspaces.detail.noPermission',\n \"You don't have permission to view this workspace.\"\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 message={tr('workspaces.detail.loading', 'Loading workspace details...')} />\n </div>\n );\n }\n\n if (error || !workspace) {\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('workspaces.detail.notFound', 'Workspace not found')}\n action={{\n label: tr('workspaces.create.backToWorkspaces', 'Back to Workspaces'),\n onClick: () => navigate(`${basePath}/workspaces`),\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 type=\"button\"\n onClick={() => navigate(`${basePath}/workspaces`)}\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('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\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 <Briefcase 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-2\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {workspace.name}\n </h1>\n <WorkspaceStatusBadge status={workspace.status} />\n </div>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n /{workspace.slug}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${workspace.organizationId}`)}\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>{tr('workspaces.detail.viewOrganization', 'View Organization')}</span>\n </button>\n {hasPermission(PERMISSIONS.WORKSPACE_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/workspaces/${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('workspaces.edit.title', 'Edit Workspace')}</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 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('workspaces.detail.detailsSection', 'Workspace 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 {workspace.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 {workspace.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 <WorkspaceStatusBadge status={workspace.status} />\n </dd>\n </div>\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('members.detail.orgId', 'Organization ID')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {workspace.organizationId}\n </dd>\n </div>\n {workspace.endpoint ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('workspaces.detail.endpoint', 'Endpoint')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {workspace.endpoint}\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('workspaces.list.members', 'Members')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {resolvedMemberCount}\n </dd>\n </div>\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(workspace.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(workspace.updatedAt)}\n </dd>\n </div>\n {workspace.region ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('workspaces.detail.region', 'Region')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {workspace.region}\n </dd>\n </div>\n ) : null}\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('workspaces.detail.membersTitle', 'Workspace members')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.detail.membersSubtitle',\n 'Add organization members to this workspace and manage access.'\n )}\n </p>\n </div>\n {hasPermission(PERMISSIONS.MEMBER_INVITE) && (\n <button\n type=\"button\"\n onClick={() => setIsAddMemberDialogOpen(true)}\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 <UserPlus size={16} aria-hidden=\"true\" />\n <span>{tr('workspaces.detail.addMember', 'Add Member')}</span>\n </button>\n )}\n </div>\n\n {membersLoading ? (\n <div className=\"py-8\">\n <LoadingState\n message={tr(\n 'workspaces.detail.loadingMembers',\n 'Loading workspace members...'\n )}\n />\n </div>\n ) : membersError ? (\n <ErrorState\n message={membersError.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetchMembers();\n },\n }}\n />\n ) : members.length > 0 ? (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {members.map((member) => (\n <article\n key={member.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-4\"\n >\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <ActorIdentity\n profile={\n profileMap[member.userId] ??\n buildActorFallback({\n actorId: member.userId,\n actorType: 'user',\n })\n }\n variant=\"inline\"\n />\n <p className=\"mt-1 text-sm text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}{' '}\n {formatDate(member.joinedAt)}\n </p>\n </div>\n <MemberRoleBadge role={member.role} />\n </div>\n\n <dl className=\"grid grid-cols-1 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-[var(--color-text-primary)]\">\n {member.invitedBy || '—'}\n </dd>\n </div>\n </dl>\n\n <button\n type=\"button\"\n onClick={() => handleRemoveMember(member.userId)}\n disabled={removingMember}\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.remove', 'Remove')}\n </button>\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-canvas)] 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=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.detail.userId', 'User ID')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </th>\n <th className=\"px-6 py-3 text-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 {members.map((member) => (\n <tr\n key={member.id}\n className=\"transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <td className=\"px-6 py-4\">\n <ActorIdentity\n profile={\n profileMap[member.userId] ??\n buildActorFallback({\n actorId: member.userId,\n actorType: 'user',\n })\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-6 py-4\">\n <MemberRoleBadge role={member.role} />\n </td>\n <td className=\"px-6 py-4 text-sm text-[var(--color-text-secondary)]\">\n {formatDate(member.joinedAt)}\n </td>\n <td className=\"px-6 py-4 break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n {member.invitedBy || '—'}\n </td>\n <td className=\"px-6 py-4 text-right\">\n <button\n type=\"button\"\n onClick={() => handleRemoveMember(member.userId)}\n disabled={removingMember}\n className=\"rounded-lg p-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label={`${tr('workspaces.removeMember.ariaPrefix', 'Remove')} ${member.userId} ${tr('workspaces.removeMember.ariaSuffix', 'from workspace')}`}\n title={`${tr('workspaces.removeMember.ariaPrefix', 'Remove')} ${member.userId} ${tr('workspaces.removeMember.ariaSuffix', 'from workspace')}`}\n >\n <Trash2 size={18} aria-hidden=\"true\" />\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n ) : (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-6\">\n <EmptyState\n icon={<Users size={40} aria-hidden=\"true\" />}\n title={tr('workspaces.detail.emptyMembersTitle', 'No workspace members yet')}\n description={tr(\n 'workspaces.detail.emptyMembersDescription',\n 'Add people from the parent organization to grant workspace access.'\n )}\n action={{\n label: tr('workspaces.detail.addMember', 'Add Member'),\n onClick: () => setIsAddMemberDialogOpen(true),\n }}\n />\n </div>\n )}\n </section>\n )}\n </div>\n </div>\n </div>\n\n <AddWorkspaceMemberDialog\n open={isAddMemberDialogOpen}\n onOpenChange={setIsAddMemberDialogOpen}\n workspaceId={workspace.id}\n organizationId={workspace.organizationId}\n existingMembers={members}\n onInviteOrganizationMember={() =>\n navigate(`${basePath}/${workspace.organizationId}/members/invite`)\n }\n />\n {ConfirmDialog}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"WorkspaceDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceDetailPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { ArrowLeft, Briefcase, Edit, Trash2, UserPlus, Users } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n ActorIdentity,\n buildActorFallback,\n useActorProfiles,\n type ActorInput,\n} from '@burdenoff/fe-libs/shared';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useWorkspace } from '../hooks/useWorkspaces';\nimport { useRemoveWorkspaceMember, useWorkspaceMembers } from '../hooks/useWorkspaceMembers';\nimport { WorkspaceStatusBadge } from '../components/WorkspaceStatusBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { EmptyState } from '../../../shared/components/feedback/EmptyState';\nimport { MemberRoleBadge } from '../../members/components/MemberRoleBadge';\nimport { AddWorkspaceMemberDialog } from '../components/AddWorkspaceMemberDialog';\nimport { useConfirmDialog } from '../../../shared/components/dialog/ConfirmDialog';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ntype ActiveTab = 'overview' | 'members';\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 WorkspaceDetailPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n const { navigate, basePath = '/organizations', apiGatewayUrl, authToken } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { workspace, loading, error } = useWorkspace(id ?? '');\n const [activeTab, setActiveTab] = useState<ActiveTab>('overview');\n const [isAddMemberDialogOpen, setIsAddMemberDialogOpen] = useState(false);\n const { openConfirmDialog, ConfirmDialog } = useConfirmDialog();\n\n const shouldFetchMembers = activeTab === 'members' && Boolean(id);\n const {\n members,\n loading: membersLoading,\n error: membersError,\n refetch: refetchMembers,\n } = useWorkspaceMembers(shouldFetchMembers ? (id ?? '') : '', 50, 0);\n const { removeWorkspaceMember, loading: removingMember } = useRemoveWorkspaceMember();\n const resolvedMemberCount =\n members.length > 0 || activeTab === 'members' ? members.length : (workspace?.memberCount ?? 0);\n\n const memberActors = useMemo<ActorInput[]>(\n () => members.map((member) => ({ actorId: member.userId, actorType: 'user' })),\n [members]\n );\n const profileMap = useActorProfiles(memberActors, {\n apiGatewayUrl,\n authToken,\n orgId: workspace?.organizationId,\n workspaceId: id,\n });\n\n const tabItems = useMemo(\n () => [\n { id: 'overview' as const, label: tr('workspaces.detail.tab.overview', 'Overview') },\n {\n id: 'members' as const,\n label: `${tr('workspaces.detail.tab.members', 'Members')} (${resolvedMemberCount})`,\n },\n ],\n [resolvedMemberCount, tr]\n );\n\n const handleRemoveMember = (userId: string) => {\n if (!id) return;\n\n openConfirmDialog({\n title: tr('workspaces.removeMember.title', 'Remove workspace member'),\n description: `${tr('workspaces.removeMember.prefix', 'Remove')} ${userId} ${tr('workspaces.removeMember.suffix', 'from this workspace? They will keep their organization membership.')}`,\n confirmText: tr('common.remove', 'Remove'),\n variant: 'danger',\n onConfirm: () => {\n void (async () => {\n try {\n const result = await removeWorkspaceMember(id, userId);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('workspaces.removeMember.failed', 'Failed to remove workspace member')\n );\n }\n\n await refetchMembers();\n toast.success(\n tr('workspaces.removeMember.success', 'Workspace member removed successfully')\n );\n } catch (removeError) {\n toast.error(\n removeError instanceof Error\n ? removeError.message\n : tr('workspaces.removeMember.failed', 'Failed to remove workspace member')\n );\n }\n })();\n },\n });\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.WORKSPACE_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 'workspaces.detail.noPermission',\n \"You don't have permission to view this workspace.\"\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 message={tr('workspaces.detail.loading', 'Loading workspace details...')} />\n </div>\n );\n }\n\n if (error || !workspace) {\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('workspaces.detail.notFound', 'Workspace not found')}\n action={{\n label: tr('workspaces.create.backToWorkspaces', 'Back to Workspaces'),\n onClick: () => navigate(`${basePath}/workspaces`),\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 type=\"button\"\n onClick={() => navigate(`${basePath}/workspaces`)}\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)]\"\n >\n <ArrowLeft size={20} aria-hidden=\"true\" />\n <span className=\"text-sm font-medium\">\n {tr('workspaces.create.backToWorkspaces', 'Back to Workspaces')}\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 <Briefcase 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-2\">\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">\n {workspace.name}\n </h1>\n <WorkspaceStatusBadge status={workspace.status} />\n </div>\n <p className=\"break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n /{workspace.slug}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/${workspace.organizationId}`)}\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>{tr('workspaces.detail.viewOrganization', 'View Organization')}</span>\n </button>\n {hasPermission(PERMISSIONS.WORKSPACE_UPDATE) && (\n <button\n type=\"button\"\n onClick={() => navigate(`${basePath}/workspaces/${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\"\n >\n <Edit size={16} aria-hidden=\"true\" />\n <span>{tr('workspaces.edit.title', 'Edit Workspace')}</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 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('workspaces.detail.detailsSection', 'Workspace 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 {workspace.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 {workspace.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 <WorkspaceStatusBadge status={workspace.status} />\n </dd>\n </div>\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('members.detail.orgId', 'Organization ID')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {workspace.organizationId}\n </dd>\n </div>\n {workspace.endpoint ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('workspaces.detail.endpoint', 'Endpoint')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-sm text-[var(--color-text-primary)]\">\n {workspace.endpoint}\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('workspaces.list.members', 'Members')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {resolvedMemberCount}\n </dd>\n </div>\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(workspace.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(workspace.updatedAt)}\n </dd>\n </div>\n {workspace.region ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-secondary)]\">\n {tr('workspaces.detail.region', 'Region')}\n </dt>\n <dd className=\"mt-1 text-sm text-[var(--color-text-primary)]\">\n {workspace.region}\n </dd>\n </div>\n ) : null}\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('workspaces.detail.membersTitle', 'Workspace members')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr(\n 'workspaces.detail.membersSubtitle',\n 'Add organization members to this workspace and manage access.'\n )}\n </p>\n </div>\n {hasPermission(PERMISSIONS.MEMBER_INVITE) && (\n <button\n type=\"button\"\n onClick={() => setIsAddMemberDialogOpen(true)}\n className=\"inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90\"\n >\n <UserPlus size={16} aria-hidden=\"true\" />\n <span>{tr('workspaces.detail.addMember', 'Add Member')}</span>\n </button>\n )}\n </div>\n\n {membersLoading ? (\n <div className=\"py-8\">\n <LoadingState\n message={tr(\n 'workspaces.detail.loadingMembers',\n 'Loading workspace members...'\n )}\n />\n </div>\n ) : membersError ? (\n <ErrorState\n message={membersError.message}\n action={{\n label: tr('common.retry', 'Retry'),\n onClick: () => {\n void refetchMembers();\n },\n }}\n />\n ) : members.length > 0 ? (\n <>\n <div className=\"grid gap-4 md:hidden\">\n {members.map((member) => (\n <article\n key={member.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-4\"\n >\n <div className=\"space-y-3\">\n <div className=\"flex items-start justify-between gap-3\">\n <div className=\"min-w-0\">\n <ActorIdentity\n profile={\n profileMap[member.userId] ??\n buildActorFallback({\n actorId: member.userId,\n actorType: 'user',\n })\n }\n variant=\"inline\"\n />\n <p className=\"mt-1 text-sm text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}{' '}\n {formatDate(member.joinedAt)}\n </p>\n </div>\n <MemberRoleBadge role={member.role} />\n </div>\n\n <dl className=\"grid grid-cols-1 gap-3 text-sm\">\n <div>\n <dt className=\"text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </dt>\n <dd className=\"mt-1 break-all font-mono text-[var(--color-text-primary)]\">\n {member.invitedBy || '—'}\n </dd>\n </div>\n </dl>\n\n <button\n type=\"button\"\n onClick={() => handleRemoveMember(member.userId)}\n disabled={removingMember}\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.remove', 'Remove')}\n </button>\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-canvas)] 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=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.detail.userId', 'User ID')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.joined', 'Joined')}\n </th>\n <th className=\"px-6 py-3 text-left text-xs font-semibold uppercase tracking-wide text-[var(--color-text-secondary)]\">\n {tr('members.list.invitedBy', 'Invited by')}\n </th>\n <th className=\"px-6 py-3 text-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 {members.map((member) => (\n <tr\n key={member.id}\n className=\"transition-colors hover:bg-[var(--color-bg-subtle)]\"\n >\n <td className=\"px-6 py-4\">\n <ActorIdentity\n profile={\n profileMap[member.userId] ??\n buildActorFallback({\n actorId: member.userId,\n actorType: 'user',\n })\n }\n variant=\"cell\"\n />\n </td>\n <td className=\"px-6 py-4\">\n <MemberRoleBadge role={member.role} />\n </td>\n <td className=\"px-6 py-4 text-sm text-[var(--color-text-secondary)]\">\n {formatDate(member.joinedAt)}\n </td>\n <td className=\"px-6 py-4 break-all font-mono text-sm text-[var(--color-text-secondary)]\">\n {member.invitedBy || '—'}\n </td>\n <td className=\"px-6 py-4 text-right\">\n <button\n type=\"button\"\n onClick={() => handleRemoveMember(member.userId)}\n disabled={removingMember}\n className=\"rounded-lg p-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50\"\n aria-label={`${tr('workspaces.removeMember.ariaPrefix', 'Remove')} ${member.userId} ${tr('workspaces.removeMember.ariaSuffix', 'from workspace')}`}\n title={`${tr('workspaces.removeMember.ariaPrefix', 'Remove')} ${member.userId} ${tr('workspaces.removeMember.ariaSuffix', 'from workspace')}`}\n >\n <Trash2 size={18} aria-hidden=\"true\" />\n </button>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n </>\n ) : (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-canvas)] p-6\">\n <EmptyState\n icon={<Users size={40} aria-hidden=\"true\" />}\n title={tr('workspaces.detail.emptyMembersTitle', 'No workspace members yet')}\n description={tr(\n 'workspaces.detail.emptyMembersDescription',\n 'Add people from the parent organization to grant workspace access.'\n )}\n action={{\n label: tr('workspaces.detail.addMember', 'Add Member'),\n onClick: () => setIsAddMemberDialogOpen(true),\n }}\n />\n </div>\n )}\n </section>\n )}\n </div>\n </div>\n </div>\n\n <AddWorkspaceMemberDialog\n open={isAddMemberDialogOpen}\n onOpenChange={setIsAddMemberDialogOpen}\n workspaceId={workspace.id}\n organizationId={workspace.organizationId}\n existingMembers={members}\n onInviteOrganizationMember={() =>\n navigate(`${basePath}/${workspace.organizationId}/members/invite`)\n }\n />\n {ConfirmDialog}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -46,7 +46,7 @@ function m() {
|
|
|
46
46
|
/* @__PURE__ */ f("button", {
|
|
47
47
|
type: "button",
|
|
48
48
|
onClick: E,
|
|
49
|
-
className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n
|
|
49
|
+
className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n ",
|
|
50
50
|
children: [/* @__PURE__ */ d(l, { size: 20 }), /* @__PURE__ */ f("span", {
|
|
51
51
|
className: "text-sm font-medium",
|
|
52
52
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WorkspaceEditPage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceEditPage.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 { useWorkspace, useUpdateWorkspace } from '../hooks/useWorkspaces';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\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 type { UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceEditPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { workspace, loading: fetchLoading, error } = useWorkspace(id!);\n const { updateWorkspace, loading: updateLoading } = useUpdateWorkspace();\n\n const handleSubmit = async (data: UpdateWorkspaceInput) => {\n if (!id) return;\n\n try {\n const result = await updateWorkspace(id, data);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ?? tr('workspaces.edit.failed', 'Failed to update workspace')\n );\n }\n\n toast.success(tr('workspaces.edit.success', 'Workspace updated successfully'));\n navigate(`${basePath}/workspaces/${id}`);\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('workspaces.edit.failed', 'Failed to update workspace')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/workspaces/${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.WORKSPACE_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('workspaces.edit.noPermission', \"You don't have permission to edit workspaces.\")}\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('workspaces.edit.loading', 'Loading workspace...')} />\n </div>\n );\n }\n\n if (error || !workspace) {\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('workspaces.detail.notFound', 'Workspace not found')}\n action={{\n label: tr('workspaces.create.backToWorkspaces', 'Back to Workspaces'),\n onClick: () => navigate(`${basePath}/workspaces`),\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=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n
|
|
1
|
+
{"version":3,"file":"WorkspaceEditPage.js","names":[],"sources":["../../../../../src/organization-management/modules/workspaces/pages/WorkspaceEditPage.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 { useWorkspace, useUpdateWorkspace } from '../hooks/useWorkspaces';\nimport { WorkspaceForm } from '../components/WorkspaceForm';\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 type { UpdateWorkspaceInput } from '../types';\n\nexport function WorkspaceEditPage() {\n const tr = useTr();\n const { id } = useParams<{ id: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { workspace, loading: fetchLoading, error } = useWorkspace(id!);\n const { updateWorkspace, loading: updateLoading } = useUpdateWorkspace();\n\n const handleSubmit = async (data: UpdateWorkspaceInput) => {\n if (!id) return;\n\n try {\n const result = await updateWorkspace(id, data);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ?? tr('workspaces.edit.failed', 'Failed to update workspace')\n );\n }\n\n toast.success(tr('workspaces.edit.success', 'Workspace updated successfully'));\n navigate(`${basePath}/workspaces/${id}`);\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('workspaces.edit.failed', 'Failed to update workspace')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/workspaces/${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.WORKSPACE_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('workspaces.edit.noPermission', \"You don't have permission to edit workspaces.\")}\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('workspaces.edit.loading', 'Loading workspace...')} />\n </div>\n );\n }\n\n if (error || !workspace) {\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('workspaces.detail.notFound', 'Workspace not found')}\n action={{\n label: tr('workspaces.create.backToWorkspaces', 'Back to Workspaces'),\n onClick: () => navigate(`${basePath}/workspaces`),\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=\"mb-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n \n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('common.backTo', 'Back to')} {workspace.name}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('workspaces.edit.title', 'Edit Workspace')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('workspaces.edit.subtitle', 'Update workspace information')}\n </p>\n </div>\n\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <WorkspaceForm\n workspace={workspace}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={updateLoading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAaA,SAAgB,IAAoB;CAClC,IAAM,IAAK,GAAO,EACZ,EAAE,UAAO,GAA2B,EACpC,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,cAAW,SAAS,GAAc,aAAU,EAAa,EAAI,EAC/D,EAAE,oBAAiB,SAAS,MAAkB,GAAoB,EAElE,IAAe,OAAO,MAA+B;AACpD,QAEL,KAAI;GACF,IAAM,IAAS,MAAM,EAAgB,GAAI,EAAK;AAC9C,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAAW,EAAG,0BAA0B,6BAA6B,CACxF;AAIH,GADA,EAAM,QAAQ,EAAG,2BAA2B,iCAAiC,CAAC,EAC9E,EAAS,GAAG,EAAS,cAAc,IAAK;WACjC,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,0BAA0B,6BAA6B,CAC/D;;IAIC,UAAqB;AACzB,IAAS,GAAG,EAAS,cAAc,IAAK;;AA2C1C,QAxCI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,iBAAiB,GAU5C,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,2BAA2B,uBAAuB,EAAI,CAAA;EAC5E,CAAA,GAIN,KAAS,CAAC,IAEV,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD;GACE,SAAS,GAAO,WAAW,EAAG,8BAA8B,sBAAsB;GAClF,QAAQ;IACN,OAAO,EAAG,sCAAsC,qBAAqB;IACrE,eAAe,EAAS,GAAG,EAAS,aAAa;IAClD;GACD,CAAA;EACE,CAAA,GAKR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OACG,EAAG,iBAAiB,UAAU;OAAC;OAAE,EAAU;OACvC;QACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,yBAAyB,iBAAiB;KAC3C,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,4BAA4B,+BAA+B;KAC7D,CAAA;IACA;MAEN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACa;KACX,UAAU;KACV,UAAU;KACV,SAAS;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MApEJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAG,gCAAgC,gDAAgD;GAChF,CAAA;EACF,CAAA"}
|
|
@@ -135,7 +135,7 @@ function E({ mode: E = "my" } = {}) {
|
|
|
135
135
|
}), A(l.WORKSPACE_CREATE) && /* @__PURE__ */ w("button", {
|
|
136
136
|
type: "button",
|
|
137
137
|
onClick: () => O(`${k}/workspaces/new`),
|
|
138
|
-
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
|
|
138
|
+
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",
|
|
139
139
|
children: [/* @__PURE__ */ C(y, {
|
|
140
140
|
size: 18,
|
|
141
141
|
"aria-hidden": "true"
|
|
@@ -196,7 +196,7 @@ function E({ mode: E = "my" } = {}) {
|
|
|
196
196
|
onClick: () => {
|
|
197
197
|
U();
|
|
198
198
|
},
|
|
199
|
-
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)]
|
|
199
|
+
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)]",
|
|
200
200
|
"aria-label": D("workspaces.list.refresh", "Refresh workspaces"),
|
|
201
201
|
title: D("workspaces.list.refresh", "Refresh workspaces"),
|
|
202
202
|
children: /* @__PURE__ */ C(oe, {
|