@burdenoff/microfe-organization 2026.1026.1 → 2026.1101.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/generated/global-operations.js +57 -58
- package/dist/generated/global-operations.js.map +1 -1
- package/dist/generated/global-types.js +1 -2
- package/dist/generated/global-types.js.map +1 -1
- package/dist/node_modules/sonner/dist/index.js +2 -3
- package/dist/node_modules/sonner/dist/index.js.map +1 -1
- package/dist/node_modules/zod/v4/classic/errors.js +44 -0
- package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/parse.js +7 -0
- package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/classic/schemas.js +652 -0
- package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/api.js +429 -0
- package/dist/node_modules/zod/v4/core/api.js.map +1 -0
- package/dist/node_modules/zod/v4/core/checks.js +143 -0
- package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
- package/dist/node_modules/zod/v4/core/core.js +84 -0
- package/dist/node_modules/zod/v4/core/core.js.map +1 -0
- package/dist/node_modules/zod/v4/core/doc.js +29 -0
- package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
- package/dist/node_modules/zod/v4/core/errors.js +90 -0
- package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js +285 -0
- package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
- package/dist/node_modules/zod/v4/core/memoizer.js +188 -0
- package/dist/node_modules/zod/v4/core/memoizer.js.map +1 -0
- package/dist/node_modules/zod/v4/core/parse.js +159 -0
- package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
- package/dist/node_modules/zod/v4/core/regexes.js +34 -0
- package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
- package/dist/node_modules/zod/v4/core/registries.js +41 -0
- package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
- package/dist/node_modules/zod/v4/core/schemas.js +979 -0
- package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js +330 -0
- package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
- package/dist/node_modules/zod/v4/core/util.js +417 -0
- package/dist/node_modules/zod/v4/core/util.js.map +1 -0
- package/dist/node_modules/zod/v4/core/versions.js +9 -0
- package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
- package/dist/node_modules/zod/v4/locales/en.js +96 -0
- package/dist/node_modules/zod/v4/locales/en.js.map +1 -0
- package/dist/organization-management/AdminOrganizationRoot.js +13 -10
- package/dist/organization-management/AdminOrganizationRoot.js.map +1 -1
- package/dist/organization-management/AdminOrganizationRoutes.js +8 -9
- package/dist/organization-management/AdminOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/UserOrganizationRoot.js +13 -10
- package/dist/organization-management/UserOrganizationRoot.js.map +1 -1
- package/dist/organization-management/UserOrganizationRoutes.js +1 -2
- package/dist/organization-management/UserOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/constants/permissions.js +1 -2
- package/dist/organization-management/constants/permissions.js.map +1 -1
- package/dist/organization-management/hooks/useOrganizationEventEmitter.js +1 -2
- package/dist/organization-management/hooks/useOrganizationEventEmitter.js.map +1 -1
- package/dist/organization-management/hooks/useOrganizationPageView.js +1 -2
- package/dist/organization-management/hooks/useOrganizationPageView.js.map +1 -1
- package/dist/organization-management/modules/members/components/BulkInviteForm.js +3 -3
- package/dist/organization-management/modules/members/components/BulkInviteForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/InviteMemberForm.js +1 -2
- package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/InviteStatusBadge.js +1 -2
- package/dist/organization-management/modules/members/components/InviteStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/members/components/MemberRoleBadge.js +1 -2
- package/dist/organization-management/modules/members/components/MemberRoleBadge.js.map +1 -1
- package/dist/organization-management/modules/members/components/MyInviteCard.js +5 -6
- package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
- package/dist/organization-management/modules/members/components/PendingInvitesList.js +8 -9
- package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
- package/dist/organization-management/modules/members/hooks/useInvites.js +2 -3
- package/dist/organization-management/modules/members/hooks/useInvites.js.map +1 -1
- package/dist/organization-management/modules/members/hooks/useMembers.js +1 -2
- package/dist/organization-management/modules/members/hooks/useMembers.js.map +1 -1
- package/dist/organization-management/modules/members/hooks/useWorkspaceInvites.js +1 -2
- package/dist/organization-management/modules/members/hooks/useWorkspaceInvites.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js +16 -17
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InvitePeoplePage.js +14 -15
- package/dist/organization-management/modules/members/pages/InvitePeoplePage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js +18 -19
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MembersListPage.js +142 -143
- package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js +168 -169
- package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +13 -14
- package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
- package/dist/organization-management/modules/members/utils/bulkInviteCsv.js +4 -10
- package/dist/organization-management/modules/members/utils/bulkInviteCsv.js.map +1 -1
- package/dist/organization-management/modules/members/utils/workspaceInvitationDisplay.js +1 -2
- package/dist/organization-management/modules/members/utils/workspaceInvitationDisplay.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js +14 -15
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js +1 -2
- package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/organizations/hooks/useOrganizations.js +2 -3
- package/dist/organization-management/modules/organizations/hooks/useOrganizations.js.map +1 -1
- package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js +22 -21
- package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +18 -19
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +29 -30
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +14 -15
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +33 -34
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/schemas/index.js +11 -12
- package/dist/organization-management/modules/organizations/schemas/index.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js +16 -24
- package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +5 -6
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +31 -32
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceStatusBadge.js +1 -2
- package/dist/organization-management/modules/workspaces/components/WorkspaceStatusBadge.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaceMembers.js +1 -2
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaceMembers.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaces.js +11 -12
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaces.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +18 -19
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +43 -44
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +19 -20
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +240 -241
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/schemas/index.js +6 -7
- package/dist/organization-management/modules/workspaces/schemas/index.js.map +1 -1
- package/dist/organization-management/providers/OrgConsoleProvider.js +1 -2
- package/dist/organization-management/providers/OrgConsoleProvider.js.map +1 -1
- package/dist/organization-management/providers/OrgScopedPermissions.js +1 -2
- package/dist/organization-management/providers/OrgScopedPermissions.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js +1 -2
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js +1 -2
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js +1 -2
- package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -2
- package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
- package/dist/organization-management/shared/components/input/SearchInput.js +1 -2
- package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js +1 -2
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js.map +1 -1
- package/dist/organization-management/shared/hooks/useDebouncedValue.js +1 -2
- package/dist/organization-management/shared/hooks/useDebouncedValue.js.map +1 -1
- package/dist/organization-management/shared/hooks/useTr.js +1 -2
- package/dist/organization-management/shared/hooks/useTr.js.map +1 -1
- package/dist/organization-management/shared/logger.js +1 -2
- package/dist/organization-management/shared/logger.js.map +1 -1
- package/dist/organization-management/shared/recaptcha.js +1 -2
- package/dist/organization-management/shared/recaptcha.js.map +1 -1
- package/dist/organization-management/shared/types/graphql.js +1 -2
- package/dist/organization-management/shared/types/graphql.js.map +1 -1
- package/dist/utils/nativeBridge.js +1 -2
- package/package.json +7 -6
- package/dist/node_modules/zod/v3/ZodError.js +0 -82
- package/dist/node_modules/zod/v3/ZodError.js.map +0 -1
- package/dist/node_modules/zod/v3/errors.js +0 -10
- package/dist/node_modules/zod/v3/errors.js.map +0 -1
- package/dist/node_modules/zod/v3/helpers/errorUtil.js +0 -9
- package/dist/node_modules/zod/v3/helpers/errorUtil.js.map +0 -1
- package/dist/node_modules/zod/v3/helpers/parseUtil.js +0 -93
- package/dist/node_modules/zod/v3/helpers/parseUtil.js.map +0 -1
- package/dist/node_modules/zod/v3/helpers/util.js +0 -76
- package/dist/node_modules/zod/v3/helpers/util.js.map +0 -1
- package/dist/node_modules/zod/v3/locales/en.js +0 -62
- package/dist/node_modules/zod/v3/locales/en.js.map +0 -1
- package/dist/node_modules/zod/v3/types.js +0 -2441
- package/dist/node_modules/zod/v3/types.js.map +0 -1
package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationForm.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n /**\n * Hide the tenant field entirely. Set when the user has access to a single\n * tenant (the shared tenant) — there is nothing to choose, so we don't show\n * a picker; `tenantId` is still submitted (and the server defaults to the\n * shared tenant when omitted).\n */\n hideTenantSelector?: boolean;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n hideTenantSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema(tr) : createOrganizationSchema(tr);\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create, and only when the user actually has a\n tenant choice — hidden for single-tenant users). */}\n {isCreateMode && !hideTenantSelector && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n aria-label={tr('organizations.form.tenant', 'Tenant')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n aria-label={tr('organizations.form.name', 'Organization Name')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n aria-label={tr('common.description', 'Description')}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n aria-label={tr('organizations.form.logoUrl', 'Logo URL')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.logoPlaceholder', 'https://example.com/logo.png')}\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.websitePlaceholder', 'https://example.com')}\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90\n \n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n \n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrganizationForm.js","names":["slugify","value","getSlugStatusClassName","tone","OrganizationForm","tr","useTr","isCreateMode","organization","currentOrganizationId","formData","setFormData","useState","tenantId","errors","setErrors","hasManualSlugEdits","setHasManualSlugEdits","slugStatus","setSlugStatus","slugRequestIdRef","useRef","lastAdjustedSlugRef","lastAdjustedBaseSlugRef","debouncedSlug","useDebouncedValue","fetchOrganizationBySlug","useGetOrganizationBySlugLazyQuery","tenantDisplayValue","useMemo","tenantName","useEffect","previousState","generatedSlug","isCancelled","requestId","baseSlug","suffix","candidateSlug","result","existingOrganizationId","validate","updateOrganizationSchema","createOrganizationSchema","error","ZodError","newErrors","path","issue","onSubmit","hideTenantSelector","loading","Loader2","onCancel"],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationForm.tsx"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\nimport { Loader2 } from 'lucide-react';\nimport { ZodError } from 'zod';\nimport { useGetOrganizationBySlugLazyQuery } from '../../../../generated/global-operations';\nimport { useDebouncedValue } from '../../../shared/hooks/useDebouncedValue';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport type { CreateOrganizationInput, UpdateOrganizationInput, Organization } from '../types';\nimport { createOrganizationSchema, updateOrganizationSchema } from '../schemas';\n\ninterface OrganizationFormProps {\n organization?: Organization | null;\n tenantId?: string;\n tenantName?: string;\n /**\n * Hide the tenant field entirely. Set when the user has access to a single\n * tenant (the shared tenant) — there is nothing to choose, so we don't show\n * a picker; `tenantId` is still submitted (and the server defaults to the\n * shared tenant when omitted).\n */\n hideTenantSelector?: boolean;\n onSubmit: (data: CreateOrganizationInput | UpdateOrganizationInput) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\ntype SlugStatus =\n | { tone: 'muted'; message: string }\n | { tone: 'success'; message: string }\n | { tone: 'warning'; message: string };\n\nfunction slugify(value: string): string {\n return value\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/-+/g, '-')\n .replace(/^-|-$/g, '');\n}\n\nfunction getSlugStatusClassName(tone: SlugStatus['tone']): string {\n switch (tone) {\n case 'success':\n return 'text-[var(--color-status-success-text)]';\n case 'warning':\n return 'text-[var(--color-status-warning-text)]';\n default:\n return 'text-[var(--color-text-muted)]';\n }\n}\n\nexport function OrganizationForm({\n organization,\n tenantId,\n tenantName,\n hideTenantSelector = false,\n onSubmit,\n onCancel,\n loading,\n}: OrganizationFormProps) {\n const tr = useTr();\n const isCreateMode = !organization;\n const currentOrganizationId = organization?.id ?? null;\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const [formData, setFormData] = useState({\n // react-doctor-disable-next-line react-doctor/no-event-handler\n name: organization?.name || '',\n slug: organization?.slug || '',\n description: organization?.description || '',\n logo: organization?.logo || '',\n website: organization?.website || '',\n // react-doctor-disable-next-line react-doctor/no-event-handler\n tenantId: tenantId || organization?.tenantId || '',\n });\n\n const [errors, setErrors] = useState<Record<string, string>>({});\n // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers\n const [hasManualSlugEdits, setHasManualSlugEdits] = useState(false);\n const [slugStatus, setSlugStatus] = useState<SlugStatus>({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n const slugRequestIdRef = useRef(0);\n const lastAdjustedSlugRef = useRef<string | null>(null);\n const lastAdjustedBaseSlugRef = useRef<string | null>(null);\n const debouncedSlug = useDebouncedValue(formData.slug, 350);\n const [fetchOrganizationBySlug] = useGetOrganizationBySlugLazyQuery({\n fetchPolicy: 'network-only',\n });\n\n const tenantDisplayValue = useMemo(() => {\n if (!formData.tenantId) {\n return '';\n }\n\n if (tenantName) {\n return `${tenantName} (${formData.tenantId})`;\n }\n\n return formData.tenantId;\n }, [formData.tenantId, tenantName]);\n\n useEffect(() => {\n if (!isCreateMode || tenantId === undefined) {\n return;\n }\n\n setFormData((previousState) => {\n if (previousState.tenantId === tenantId) {\n return previousState;\n }\n\n return {\n ...previousState,\n tenantId,\n };\n });\n }, [isCreateMode, tenantId]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n const generatedSlug = slugify(formData.name);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n\n // react-doctor-disable-next-line react-doctor/no-event-handler\n if (!hasManualSlugEdits) {\n setFormData((previousState) => ({\n ...previousState,\n slug: generatedSlug,\n }));\n }\n\n if (!generatedSlug) {\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n }\n }, [formData.name, hasManualSlugEdits, isCreateMode, tr]);\n\n useEffect(() => {\n if (!isCreateMode) {\n return;\n }\n\n if (!formData.tenantId || !debouncedSlug) {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'muted',\n message: tr(\n 'organizations.form.slugAutoGenerated',\n 'Slug is auto-generated from the organization name.'\n ),\n });\n return;\n }\n\n if (lastAdjustedSlugRef.current === debouncedSlug && lastAdjustedBaseSlugRef.current !== null) {\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${debouncedSlug}” ${tr('organizations.form.slugBecause', 'because')} “${lastAdjustedBaseSlugRef.current}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n return;\n }\n\n let isCancelled = false;\n const requestId = ++slugRequestIdRef.current;\n\n const resolveAvailableSlug = async () => {\n setSlugStatus({\n tone: 'muted',\n message: tr('organizations.form.slugChecking', 'Checking slug availability…'),\n });\n\n const baseSlug = debouncedSlug;\n let suffix = 2;\n let candidateSlug = baseSlug;\n\n while (true) {\n const result = await fetchOrganizationBySlug({\n variables: {\n tenantId: formData.tenantId,\n slug: candidateSlug,\n },\n });\n\n if (isCancelled || slugRequestIdRef.current !== requestId) {\n return;\n }\n\n const existingOrganizationId = (\n result.data?.organizationBySlug as { id?: string } | null | undefined\n )?.id;\n if (!existingOrganizationId || existingOrganizationId === currentOrganizationId) {\n if (candidateSlug !== baseSlug) {\n lastAdjustedSlugRef.current = candidateSlug;\n lastAdjustedBaseSlugRef.current = baseSlug;\n setFormData((previousState) => {\n if (previousState.slug !== baseSlug) {\n return previousState;\n }\n\n return {\n ...previousState,\n slug: candidateSlug,\n };\n });\n\n setSlugStatus({\n tone: 'warning',\n message: `${tr('organizations.form.slugAdjustedTo', 'Slug adjusted to')} “${candidateSlug}” ${tr('organizations.form.slugBecause', 'because')} “${baseSlug}” ${tr('organizations.form.slugAlreadyTaken', 'is already taken.')}`,\n });\n } else {\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setSlugStatus({\n tone: 'success',\n message: `${tr('organizations.form.slugPrefix', 'Slug')} “${candidateSlug}” ${tr('organizations.form.slugAvailable', 'is available.')}`,\n });\n }\n\n return;\n }\n\n candidateSlug = `${baseSlug}-${suffix}`;\n suffix += 1;\n }\n };\n\n void resolveAvailableSlug();\n\n return () => {\n isCancelled = true;\n };\n }, [\n currentOrganizationId,\n debouncedSlug,\n fetchOrganizationBySlug,\n formData.tenantId,\n isCreateMode,\n tr,\n ]);\n\n const validate = () => {\n try {\n const schema = organization ? updateOrganizationSchema(tr) : createOrganizationSchema(tr);\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create, and only when the user actually has a\n tenant choice — hidden for single-tenant users). */}\n {isCreateMode && !hideTenantSelector && (\n <div>\n <label\n htmlFor=\"tenantId\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.tenant', 'Tenant')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n {tenantId ? (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={tenantDisplayValue}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-muted)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n disabled:cursor-not-allowed\n \"\n disabled\n readOnly\n aria-label={tr('organizations.form.selectedTenant', 'Selected tenant')}\n />\n ) : (\n <input\n id=\"tenantId\"\n type=\"text\"\n value={formData.tenantId}\n onChange={(e) => setFormData({ ...formData, tenantId: e.target.value })}\n aria-label={tr('organizations.form.tenant', 'Tenant')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.tenantPlaceholder', 'Enter tenant ID')}\n disabled={loading}\n required\n autoComplete=\"off\"\n />\n )}\n {errors.tenantId && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.tenantId}</p>\n )}\n </div>\n )}\n\n {/* Name */}\n <div>\n <label\n htmlFor=\"name\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.name', 'Organization Name')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"name\"\n type=\"text\"\n value={formData.name}\n onChange={(e) => setFormData({ ...formData, name: e.target.value })}\n aria-label={tr('organizations.form.name', 'Organization Name')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.namePlaceholder', 'Enter organization name')}\n disabled={loading}\n required\n autoComplete=\"organization\"\n />\n {errors.name && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.name}</p>\n )}\n </div>\n\n {/* Slug */}\n <div>\n <label\n htmlFor=\"slug\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.slug', 'Slug')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <input\n id=\"slug\"\n type=\"text\"\n value={formData.slug}\n aria-label={tr('common.slug', 'Slug')}\n onChange={(e) => {\n setHasManualSlugEdits(true);\n lastAdjustedSlugRef.current = null;\n lastAdjustedBaseSlugRef.current = null;\n setFormData({\n ...formData,\n slug: slugify(e.target.value),\n });\n }}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.slugPlaceholder', 'organization-slug')}\n disabled={loading || !!organization}\n required={!organization}\n autoComplete=\"off\"\n />\n <p\n className={`text-xs mt-1 ${getSlugStatusClassName(\n isCreateMode ? slugStatus.tone : 'muted'\n )}`}\n >\n {isCreateMode\n ? slugStatus.message\n : tr(\n 'organizations.form.slugImmutable',\n 'Lowercase letters, numbers, and hyphens only. Cannot be changed after creation.'\n )}\n </p>\n {errors.slug && (\n <p className=\"mt-1 text-sm text-[var(--color-status-error-text)]\">{errors.slug}</p>\n )}\n </div>\n\n {/* Description */}\n <div>\n <label\n htmlFor=\"description\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('common.description', 'Description')}\n </label>\n <textarea\n id=\"description\"\n value={formData.description}\n onChange={(e) => setFormData({ ...formData, description: e.target.value })}\n aria-label={tr('common.description', 'Description')}\n rows={4}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr(\n 'organizations.form.descriptionPlaceholder',\n 'Enter organization description'\n )}\n disabled={loading}\n autoComplete=\"off\"\n />\n </div>\n\n {/* Logo URL */}\n <div>\n <label\n htmlFor=\"logo\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.form.logoUrl', 'Logo URL')}\n </label>\n <input\n id=\"logo\"\n type=\"url\"\n value={formData.logo}\n onChange={(e) => setFormData({ ...formData, logo: e.target.value })}\n aria-label={tr('organizations.form.logoUrl', 'Logo URL')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.logoPlaceholder', 'https://example.com/logo.png')}\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder={tr('organizations.form.websitePlaceholder', 'https://example.com')}\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90\n \n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n \n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":";;;;;;;;;AA8BA,SAASA,EAAQ,GAAuB;CACtC,OAAOC,EACJ,YAAY,CAAC,CACb,KAAK,CAAC,CACN,QAAQ,iBAAiB,EAAE,CAAC,CAC5B,QAAQ,QAAQ,GAAG,CAAC,CACpB,QAAQ,OAAO,GAAG,CAAC,CACnB,QAAQ,UAAU,EAAE;AACzB;AAEA,SAASC,EAAuB,GAAkC;CAChE,QAAQC,GAAR;EACE,KAAK,WACH,OAAO;EACT,KAAK,WACH,OAAO;EACT,SACE,OAAO;CACX;AACF;AAEA,SAAgBC,EAAiB,EAC/B,iBACA,aACA,eACA,wBAAqB,IACrB,aACA,aACA,cACwB;CACxB,IAAMC,IAAKC,EAAM,GACXC,IAAe,CAACC,GAChBC,IAAwBD,GAAc,MAAM,MAE5C,CAACE,GAAUC,KAAeC,EAAS;EAEvC,MAAMJ,GAAc,QAAQ;EAC5B,MAAMA,GAAc,QAAQ;EAC5B,aAAaA,GAAc,eAAe;EAC1C,MAAMA,GAAc,QAAQ;EAC5B,SAASA,GAAc,WAAW;EAElC,UAAUK,KAAYL,GAAc,YAAY;CAClD,CAAC,GAEK,CAACM,GAAQC,KAAaH,EAAiC,CAAC,CAAC,GAEzD,CAACI,GAAoBC,KAAyBL,EAAS,EAAK,GAC5D,CAACM,GAAYC,KAAiBP,EAAqB;EACvD,MAAM;EACN,SAASP,EACP,wCACA,oDACF;CACF,CAAC,GACKe,IAAmBC,EAAO,CAAC,GAC3BC,IAAsBD,EAAsB,IAAI,GAChDE,IAA0BF,EAAsB,IAAI,GACpDG,IAAgBC,EAAkBf,EAAS,MAAM,GAAG,GACpD,CAACgB,KAA2BC,EAAkC,EAClE,aAAa,eACf,CAAC,GAEKC,IAAqBC,QACpBnB,EAAS,WAIVoB,IACK,GAAGA,EAAW,IAAIpB,EAAS,SAAS,KAGtCA,EAAS,WAPP,IAQR,CAACA,EAAS,UAAUoB,CAAU,CAAC;CAgDlC,AA9CAC,QAAgB;EACd,AAAKxB,KAAgBM,MAAa,KAAA,KAIlCF,GAAa,MACPqB,EAAc,aAAanB,IACtBmB,IAGF;GACL,GAAGA;GACH;EACF,CACD;CACH,GAAG,CAACzB,GAAcM,CAAQ,CAAC,GAE3BkB,QAAgB;EACd,IAAI,CAACxB,GACH;EAIF,IAAM0B,IAAgBjC,EAAQU,EAAS,IAAI;EAY3C,AAXA,EAAoB,UAAU,MAC9B,EAAwB,UAAU,MAG7BM,KACHL,GAAa,OAAmB;GAC9B,GAAGqB;GACH,MAAMC;EACR,EAAE,GAGCA,KACHd,EAAc;GACZ,MAAM;GACN,SAASd,EACP,wCACA,oDACF;EACF,CAAC;CAEL,GAAG;EAACK,EAAS;EAAMM;EAAoBT;EAAcF;CAAE,CAAC,GAExD0B,QAAgB;EACd,IAAI,CAACxB,GACH;EAGF,IAAI,CAACG,EAAS,YAAY,CAACc,GAAe;GAGxC,AAFA,EAAoB,UAAU,MAC9B,EAAwB,UAAU,MAClCL,EAAc;IACZ,MAAM;IACN,SAASd,EACP,wCACA,oDACF;GACF,CAAC;GACD;EACF;EAEA,IAAIiB,EAAoB,YAAYE,KAAiBD,EAAwB,YAAY,MAAM;GAC7F,EAAc;IACZ,MAAM;IACN,SAAS,GAAGlB,EAAG,qCAAqC,kBAAkB,EAAE,IAAImB,EAAc,IAAInB,EAAG,kCAAkC,SAAS,EAAE,IAAIkB,EAAwB,QAAQ,IAAIlB,EAAG,uCAAuC,mBAAmB;GACrP,CAAC;GACD;EACF;EAEA,IAAI6B,IAAc,IACZC,IAAY,EAAEf,EAAiB;EAiErC,QAFA,YA7DyC;GACvC,EAAc;IACZ,MAAM;IACN,SAASf,EAAG,mCAAmC,6BAA6B;GAC9E,CAAC;GAED,IAAM+B,IAAWZ,GACba,IAAS,GACTC,IAAgBF;GAEpB,SAAa;IACX,IAAMG,IAAS,MAAMb,EAAwB,EAC3C,WAAW;KACT,UAAUhB,EAAS;KACnB,MAAM4B;IACR,EACF,CAAC;IAED,IAAIJ,KAAed,EAAiB,YAAYe,GAC9C;IAGF,IAAMK,IACJD,EAAO,MAAM,oBACZ;IACH,IAAI,CAACC,KAA0BA,MAA2B/B,GAAuB;KAC/E,AAAI6B,MAAkBF,KAmBpB,EAAoB,UAAU,MAC9B,EAAwB,UAAU,MAClCjB,EAAc;MACZ,MAAM;MACN,SAAS,GAAGd,EAAG,iCAAiC,MAAM,EAAE,IAAIiC,EAAc,IAAIjC,EAAG,oCAAoC,eAAe;KACtI,CAAC,MAvBD,EAAoB,UAAUiC,GAC9B,EAAwB,UAAUF,GAClCzB,GAAa,MACPqB,EAAc,SAASI,IAIpB;MACL,GAAGJ;MACH,MAAMM;KACR,IANSN,CAOV,GAEDb,EAAc;MACZ,MAAM;MACN,SAAS,GAAGd,EAAG,qCAAqC,kBAAkB,EAAE,IAAIiC,EAAc,IAAIjC,EAAG,kCAAkC,SAAS,EAAE,IAAI+B,EAAS,IAAI/B,EAAG,uCAAuC,mBAAmB;KAC9N,CAAC;KAUH;IACF;IAGA,AADA,IAAgB,GAAG+B,EAAS,GAAGC,KAC/B,KAAU;GACZ;EACF,EAEK,CAAqB,SAEb;GACX,IAAc;EAChB;CACF,GAAG;EACD5B;EACAe;EACAE;EACAhB,EAAS;EACTH;EACAF;CACF,CAAC;CAED,IAAMoC,UAAiB;EACrB,IAAI;GAIF,QAHejC,IAAekC,EAAyBrC,CAAE,IAAIsC,EAAyBtC,CAAE,EAAA,CACjF,MAAMK,CAAQ,GACrBK,EAAU,CAAC,CAAC,GACL;EACT,SAAS6B,GAAO;GACd,IAAIA,aAAiBC,GAAU;IAC7B,IAAMC,IAAoC,CAAC;IAK3C,AAJAF,EAAM,OAAO,SAAS,MAAU;KAC9B,IAAMG,IAAOC,EAAM,KAAK,KAAK,GAAG;KAChC,EAAUD,KAAQC,EAAM;IAC1B,CAAC,GACDjC,EAAU+B,CAAS;GACrB;GACA,OAAO;EACT;CACF;CA+BA,OACE,kBAAC,QAAD;EAAM,UAAU,OA9BU,MAAuB;GAGjD,AAFA,EAAE,eAAe,GAEZL,EAAS,KAuBd,MAAMQ,EAnBazC,IACf;IAEE,MAAME,EAAS,SAASF,EAAa,OAAuB,KAAA,IAAhBE,EAAS;IACrD,aACEA,EAAS,gBAAgBF,EAAa,cAAqC,KAAA,IAAvBE,EAAS;IAC/D,MAAMA,EAAS,SAASF,EAAa,OAAuB,KAAA,IAAhBE,EAAS;IACrD,SAASA,EAAS,YAAYF,EAAa,UAA6B,KAAA,IAAnBE,EAAS;GAChE,IACA;IAEE,UAAUA,EAAS;IACnB,MAAMA,EAAS;IACf,MAAMA,EAAS;IACf,aAAaA,EAAS,eAAe,KAAA;IACrC,MAAMA,EAAS,QAAQ,KAAA;IACvB,SAASA,EAAS,WAAW,KAAA;GAC/B,CAEqB;EAC3B;EAGgC,WAAU;EAAxC,UAAA;GAGGH,KAAgB,CAAC2C,KAChB,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;KAFZ,UAAA;MAIG7C,EAAG,6BAA6B,QAAQ;MAAG;MAC5C,kBAAC,QAAD;OAAM,WAAU;OAAwC,UAAA;MAAO,CAAA;KAC1D;;IACNQ,IACC,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAOe;KACP,WAAU;KASV,UAAA;KACA,UAAA;KACA,cAAYvB,EAAG,qCAAqC,iBAAiB;IACtE,CAAA,IAED,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAOK,EAAS;KAChB,WAAW,MAAMC,EAAY;MAAE,GAAGD;MAAU,UAAU,EAAE,OAAO;KAAM,CAAC;KACtE,cAAYL,EAAG,6BAA6B,QAAQ;KACpD,WAAU;KAUV,aAAaA,EAAG,wCAAwC,iBAAiB;KACzE,UAAU8C;KACV,UAAA;KACA,cAAa;IACd,CAAA;IAEFrC,EAAO,YACN,kBAAC,KAAD;KAAG,WAAU;KAAsD,UAAAA,EAAO;IAAY,CAAA;GAErF,EAAA,CAAA;GAIP,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;KAFZ,UAAA;MAIGT,EAAG,2BAA2B,mBAAmB;MAAG;MACrD,kBAAC,QAAD;OAAM,WAAU;OAAwC,UAAA;MAAO,CAAA;KAC1D;;IACP,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAOK,EAAS;KAChB,WAAW,MAAMC,EAAY;MAAE,GAAGD;MAAU,MAAM,EAAE,OAAO;KAAM,CAAC;KAClE,cAAYL,EAAG,2BAA2B,mBAAmB;KAC7D,WAAU;KAUV,aAAaA,EAAG,sCAAsC,yBAAyB;KAC/E,UAAU8C;KACV,UAAA;KACA,cAAa;IACd,CAAA;IACArC,EAAO,QACN,kBAAC,KAAD;KAAG,WAAU;KAAsD,UAAAA,EAAO;IAAQ,CAAA;GAEjF,EAAA,CAAA;GAGL,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;KAFZ,UAAA;MAIGT,EAAG,eAAe,MAAM;MAAG;MAC5B,kBAAC,QAAD;OAAM,WAAU;OAAwC,UAAA;MAAO,CAAA;KAC1D;;IACP,kBAAC,SAAD;KACE,IAAG;KACH,MAAK;KACL,OAAOK,EAAS;KAChB,cAAYL,EAAG,eAAe,MAAM;KACpC,WAAW,MAAM;MAIf,AAHAY,EAAsB,EAAI,GAC1B,EAAoB,UAAU,MAC9B,EAAwB,UAAU,MAClCN,EAAY;OACV,GAAGD;OACH,MAAMV,EAAQ,EAAE,OAAO,KAAK;MAC9B,CAAC;KACH;KACA,WAAU;KAUV,aAAaK,EAAG,sCAAsC,mBAAmB;KACzE,UAAU8C,KAAW,CAAC,CAAC3C;KACvB,UAAU,CAACA;KACX,cAAa;IACd,CAAA;IACD,kBAAC,KAAD;KACE,WAAW,gBAAgBN,EACzBK,IAAeW,EAAW,OAAO,OACnC;KAEC,UAAAX,IACGW,EAAW,UACXb,EACE,oCACA,iFACF;IACH,CAAA;IACFS,EAAO,QACN,kBAAC,KAAD;KAAG,WAAU;KAAsD,UAAAA,EAAO;IAAQ,CAAA;GAEjF,EAAA,CAAA;GAGL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;IAET,UAAAT,EAAG,sBAAsB,aAAa;GAClC,CAAA,GACP,kBAAC,YAAD;IACE,IAAG;IACH,OAAOK,EAAS;IAChB,WAAW,MAAMC,EAAY;KAAE,GAAGD;KAAU,aAAa,EAAE,OAAO;IAAM,CAAC;IACzE,cAAYL,EAAG,sBAAsB,aAAa;IAClD,MAAM;IACN,WAAU;IAUV,aAAaA,EACX,6CACA,gCACF;IACA,UAAU8C;IACV,cAAa;GACd,CAAA,CACE,EAAA,CAAA;GAGL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;IAET,UAAA9C,EAAG,8BAA8B,UAAU;GACvC,CAAA,GACP,kBAAC,SAAD;IACE,IAAG;IACH,MAAK;IACL,OAAOK,EAAS;IAChB,WAAW,MAAMC,EAAY;KAAE,GAAGD;KAAU,MAAM,EAAE,OAAO;IAAM,CAAC;IAClE,cAAYL,EAAG,8BAA8B,UAAU;IACvD,WAAU;IAUV,aAAaA,EAAG,sCAAsC,8BAA8B;IACpF,UAAU8C;IACV,cAAa;GACd,CAAA,CACE,EAAA,CAAA;GAGL,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;IAET,UAAA9C,EAAG,gCAAgC,SAAS;GACxC,CAAA,GACP,kBAAC,SAAD;IACE,IAAG;IACH,MAAK;IACL,OAAOK,EAAS;IAChB,WAAW,MAAMC,EAAY;KAAE,GAAGD;KAAU,SAAS,EAAE,OAAO;IAAM,CAAC;IACrE,cAAYL,EAAG,gCAAgC,SAAS;IACxD,WAAU;IAUV,aAAaA,EAAG,yCAAyC,qBAAqB;IAC9E,UAAU8C;IACV,cAAa;GACd,CAAA,CACE,EAAA,CAAA;GAGL,kBAAC,OAAD;IAAK,WAAU;IAAf,UAAA,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAUA;KACV,WAAU;KAHZ,UAAA,CAgBGA,KAAW,kBAACC,GAAD;MAAS,MAAM;MAAI,WAAU;KAAgB,CAAA,GACxD5C,IACGH,EAAG,mCAAmC,qBAAqB,IAC3DA,EAAG,+BAA+B,qBAAqB,CACrD;IAER,CAAA,GAAA,kBAAC,UAAD;KACE,MAAK;KACL,SAASgD;KACT,UAAUF;KACV,WAAU;KAcT,UAAA9C,EAAG,iBAAiB,QAAQ;IACvB,CAAA,CACL;;EACD;;AAEV"}
|
package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useCallback as t } from "react";
|
|
|
3
3
|
import { CircleAlert as n, CircleCheck as r, CircleX as i } from "lucide-react";
|
|
4
4
|
import { jsx as a, jsxs as o } from "react/jsx-runtime";
|
|
5
5
|
import { useI18n as s } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
6
|
-
|
|
6
|
+
// src/organization-management/modules/organizations/components/OrganizationStatusBadge.tsx
|
|
7
7
|
function c({ status: c, showIcon: l = !0 }) {
|
|
8
8
|
let { t: u } = s(), d = t((e, t) => {
|
|
9
9
|
let n = u(e);
|
|
@@ -47,7 +47,6 @@ function c({ status: c, showIcon: l = !0 }) {
|
|
|
47
47
|
}), f.label]
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
-
//#endregion
|
|
51
50
|
export { c as OrganizationStatusBadge };
|
|
52
51
|
|
|
53
52
|
//# sourceMappingURL=OrganizationStatusBadge.js.map
|
package/dist/organization-management/modules/organizations/components/OrganizationStatusBadge.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationStatusBadge.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationStatusBadge.tsx"],"sourcesContent":["import { CircleCheck, CircleX, CircleAlert } from 'lucide-react';\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { OrganizationStatus } from '../types';\n\ninterface OrganizationStatusBadgeProps {\n status: OrganizationStatus;\n showIcon?: boolean;\n}\n\n/**\n * OrganizationStatusBadge\n * Displays the status of an organization with color coding and optional icon\n */\nexport function OrganizationStatusBadge({ status, showIcon = true }: OrganizationStatusBadgeProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n const getStatusConfig = () => {\n switch (status) {\n case OrganizationStatus.ACTIVE:\n return {\n label: tr('organizations.status.active', 'Active'),\n icon: CircleCheck,\n pulse: true,\n className:\n 'bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]',\n };\n case OrganizationStatus.INACTIVE:\n return {\n label: tr('organizations.status.inactive', 'Inactive'),\n icon: CircleX,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n case OrganizationStatus.ARCHIVED:\n return {\n label: tr('organizations.status.archived', 'Archived'),\n icon: CircleAlert,\n className:\n 'bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]',\n };\n default:\n return {\n label: status,\n icon: CircleAlert,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n }\n };\n\n const config = getStatusConfig();\n const Icon = config.icon;\n\n return (\n <span\n className={`\n inline-flex items-center gap-1.5 px-2.5 py-1\n rounded-full text-xs font-medium\n border\n transition-colors duration-200\n ${config.className}\n `}\n >\n {showIcon && (\n <Icon\n size={14}\n className={'pulse' in config && config.pulse ? 'motion-safe:animate-vc-glow-pulse' : ''}\n />\n )}\n {config.label}\n </span>\n );\n}\n"],"mappings":";;;;;;AAcA,
|
|
1
|
+
{"version":3,"file":"OrganizationStatusBadge.js","names":["OrganizationStatusBadge","useI18n","tr","useCallback","translated","t","key","fallback","config","status","OrganizationStatus","CircleCheck","CircleX","CircleAlert","Icon","showIcon"],"sources":["../../../../../src/organization-management/modules/organizations/components/OrganizationStatusBadge.tsx"],"sourcesContent":["import { CircleCheck, CircleX, CircleAlert } from 'lucide-react';\nimport { useCallback } from 'react';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { OrganizationStatus } from '../types';\n\ninterface OrganizationStatusBadgeProps {\n status: OrganizationStatus;\n showIcon?: boolean;\n}\n\n/**\n * OrganizationStatusBadge\n * Displays the status of an organization with color coding and optional icon\n */\nexport function OrganizationStatusBadge({ status, showIcon = true }: OrganizationStatusBadgeProps) {\n const { t } = useI18n();\n const tr = useCallback(\n (key: string, fallback: string) => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n },\n [t]\n );\n const getStatusConfig = () => {\n switch (status) {\n case OrganizationStatus.ACTIVE:\n return {\n label: tr('organizations.status.active', 'Active'),\n icon: CircleCheck,\n pulse: true,\n className:\n 'bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)] border-[var(--color-status-success-border)]',\n };\n case OrganizationStatus.INACTIVE:\n return {\n label: tr('organizations.status.inactive', 'Inactive'),\n icon: CircleX,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n case OrganizationStatus.ARCHIVED:\n return {\n label: tr('organizations.status.archived', 'Archived'),\n icon: CircleAlert,\n className:\n 'bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)] border-[var(--color-status-warning-border)]',\n };\n default:\n return {\n label: status,\n icon: CircleAlert,\n className:\n 'bg-[var(--color-bg-muted)] text-[var(--color-text-secondary)] border-[var(--color-border-default)]',\n };\n }\n };\n\n const config = getStatusConfig();\n const Icon = config.icon;\n\n return (\n <span\n className={`\n inline-flex items-center gap-1.5 px-2.5 py-1\n rounded-full text-xs font-medium\n border\n transition-colors duration-200\n ${config.className}\n `}\n >\n {showIcon && (\n <Icon\n size={14}\n className={'pulse' in config && config.pulse ? 'motion-safe:animate-vc-glow-pulse' : ''}\n />\n )}\n {config.label}\n </span>\n );\n}\n"],"mappings":";;;;;;AAcA,SAAgBA,EAAwB,EAAE,WAAQ,cAAW,MAAsC;CACjG,IAAM,EAAE,SAAMC,EAAQ,GAChBC,IAAKC,GACR,GAAa,MAAqB;EACjC,IAAMC,IAAaC,EAAEC,CAAG;EACxB,OAAOF,MAAeE,IAAMC,IAAWH;CACzC,GACA,CAACC,CAAC,CACJ,GAmCMG,WAlCwB;EAC5B,QAAQC,GAAR;GACE,KAAKC,EAAmB,QACtB,OAAO;IACL,OAAOR,EAAG,+BAA+B,QAAQ;IACjD,MAAMS;IACN,OAAO;IACP,WACE;GACJ;GACF,KAAKD,EAAmB,UACtB,OAAO;IACL,OAAOR,EAAG,iCAAiC,UAAU;IACrD,MAAMU;IACN,WACE;GACJ;GACF,KAAKF,EAAmB,UACtB,OAAO;IACL,OAAOR,EAAG,iCAAiC,UAAU;IACrD,MAAMW;IACN,WACE;GACJ;GACF,SACE,OAAO;IACL,OAAOJ;IACP,MAAMI;IACN,WACE;GACJ;EACJ;CACF,EAEe,CAAgB,GACzBC,IAAON,EAAO;CAEpB,OACE,kBAAC,QAAD;EACE,WAAW;;;;;UAKPA,EAAO,UAAU;;EANvB,UAAA,CASGO,KACC,kBAACD,GAAD;GACE,MAAM;GACN,WAAW,WAAWN,KAAUA,EAAO,QAAQ,sCAAsC;EACtF,CAAA,GAEFA,EAAO,KACJ;;AAEV"}
|
|
@@ -2,7 +2,7 @@ import { useCreateOrganizationMutation as e, useDeleteOrganizationMutation as t,
|
|
|
2
2
|
import { useOrganizationEventEmitter as o } from "../../../hooks/useOrganizationEventEmitter.js";
|
|
3
3
|
import { logger as s, redactId as c } from "../../../shared/logger.js";
|
|
4
4
|
import { executeRecaptcha as l } from "../../../shared/recaptcha.js";
|
|
5
|
-
|
|
5
|
+
// src/organization-management/modules/organizations/hooks/useOrganizations.ts
|
|
6
6
|
function u(e) {
|
|
7
7
|
return e?.graphQLErrors?.[0]?.extensions?.code;
|
|
8
8
|
}
|
|
@@ -74,7 +74,7 @@ function h() {
|
|
|
74
74
|
createOrganization: async (e) => {
|
|
75
75
|
s.info("mutation.start", { name: "createOrganization" });
|
|
76
76
|
try {
|
|
77
|
-
let r = await l("organization_create"), i = await n({ variables: { input: {
|
|
77
|
+
let r = await /* @__PURE__ */ l("organization_create"), i = await n({ variables: { input: {
|
|
78
78
|
...e,
|
|
79
79
|
...r ? { recaptchaToken: r } : {}
|
|
80
80
|
} } }), a = i.data?.createOrganization?.id;
|
|
@@ -180,7 +180,6 @@ function _() {
|
|
|
180
180
|
error: a
|
|
181
181
|
};
|
|
182
182
|
}
|
|
183
|
-
//#endregion
|
|
184
183
|
export { h as useCreateOrganization, _ as useDeleteOrganization, p as useMyOrganizations, m as useOrganization, f as useOrganizations, g as useUpdateOrganization };
|
|
185
184
|
|
|
186
185
|
//# sourceMappingURL=useOrganizations.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrganizations.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrganizations.ts"],"sourcesContent":["import {\n useGetOrganizationsQuery,\n useGetMyOrganizationsQuery,\n useGetOrganizationQuery,\n useGetOrganizationBySlugQuery,\n useGetOrganizationsByTenantQuery,\n useCreateOrganizationMutation,\n useUpdateOrganizationMutation,\n useDeleteOrganizationMutation,\n} from '../../../../generated/global-operations';\nimport type {\n Organization,\n OrganizationFilterInput,\n CreateOrganizationInput,\n UpdateOrganizationInput,\n} from '../types';\nimport { useOrganizationEventEmitter } from '../../../hooks/useOrganizationEventEmitter';\nimport { logger, redactId } from '../../../shared/logger';\nimport { executeRecaptcha } from '../../../shared/recaptcha';\n\ninterface GraphqlExtensions {\n code?: string;\n}\n\ninterface GraphqlError {\n message?: string;\n extensions?: GraphqlExtensions;\n}\n\ninterface ApolloLikeError {\n message?: string;\n graphQLErrors?: ReadonlyArray<GraphqlError>;\n}\n\nfunction extractErrorCode(error: unknown): string | undefined {\n const apolloError = error as ApolloLikeError | undefined;\n return apolloError?.graphQLErrors?.[0]?.extensions?.code;\n}\n\nfunction extractErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n const apolloError = error as ApolloLikeError | undefined;\n return apolloError?.message ?? 'Unknown error';\n}\n\n/**\n * Hook to fetch organizations with filtering and pagination\n *\n * Calls the platform-wide `allOrganizations` admin query. For the end-user\n * \"my orgs\" view use `useMyOrganizations`.\n */\nexport function useOrganizations(\n filter?: OrganizationFilterInput,\n limit: number = 20,\n offset: number = 0,\n enabled: boolean = true\n) {\n const { data, loading, error, refetch, fetchMore } = useGetOrganizationsQuery({\n variables: { filter, limit, offset },\n skip: !enabled,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.allOrganizations?.items as Organization[]) || [],\n total: data?.allOrganizations?.total || 0,\n limit: data?.allOrganizations?.limit || limit,\n offset: data?.allOrganizations?.offset || offset,\n hasMore: data?.allOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to fetch organizations the current user is a member of (owned + added to).\n *\n * Backed by the `myOrganizations` query which uses @rbac(scopeType: \"self\") —\n * authenticated user only, no permission check, resolver filters by actorId.\n * Use this in product-app contexts where the user should see *their* orgs.\n *\n * Filter input is intentionally not supported — `myOrganizations` returns the\n * full membership list and any further filtering should be done client-side\n * (or via a future `myOrganizationsFiltered` query if the use-case warrants it).\n */\nexport function useMyOrganizations(\n limit: number = 20,\n offset: number = 0,\n enabled: boolean = true\n) {\n const { data, loading, error, refetch, fetchMore } = useGetMyOrganizationsQuery({\n variables: { limit, offset },\n skip: !enabled,\n // First mount: use cache if present, then revalidate from the network in\n // the background. Subsequent renders within the same session reuse the\n // cache. This keeps the list fresh when the user navigates back after\n // accepting an invite in another tab without paying the round-trip on\n // every re-render.\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.myOrganizations?.items as Organization[]) || [],\n total: data?.myOrganizations?.total || 0,\n limit: data?.myOrganizations?.limit || limit,\n offset: data?.myOrganizations?.offset || offset,\n hasMore: data?.myOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to fetch a single organization by ID\n */\nexport function useOrganization(id: string) {\n const { data, loading, error, refetch } = useGetOrganizationQuery({\n variables: { id },\n skip: !id,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n const organization = data?.organization as Organization | undefined;\n\n return {\n organization,\n loading: loading && !organization,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to fetch an organization by slug\n */\nexport function useOrganizationBySlug(tenantId: string, slug: string) {\n const { data, loading, error, refetch } = useGetOrganizationBySlugQuery({\n variables: { tenantId, slug },\n skip: !tenantId || !slug,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organization: data?.organizationBySlug as Organization | undefined,\n loading,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to fetch organizations by tenant ID\n */\nexport function useOrganizationsByTenant(tenantId: string, limit: number = 20, offset: number = 0) {\n const { data, loading, error, refetch, fetchMore } = useGetOrganizationsByTenantQuery({\n variables: { tenantId, limit, offset },\n skip: !tenantId,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.allOrganizations?.items as Organization[]) || [],\n total: data?.allOrganizations?.total || 0,\n limit: data?.allOrganizations?.limit || limit,\n offset: data?.allOrganizations?.offset || offset,\n hasMore: data?.allOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to create a new organization\n */\nexport function useCreateOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [createOrganization, { data, loading, error }] = useCreateOrganizationMutation();\n\n return {\n createOrganization: async (input: CreateOrganizationInput) => {\n logger.info('mutation.start', { name: 'createOrganization' });\n try {\n const recaptchaToken = await executeRecaptcha('organization_create');\n const result = await createOrganization({\n variables: {\n input: {\n ...input,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n },\n },\n });\n const organizationId = result.data?.createOrganization?.id;\n emit('organization.created', { organizationId, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'createOrganization',\n organizationId: redactId(organizationId),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.create_failed', {\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n { name: 'createOrganization', errorCode, errorMessage },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n\n/**\n * Hook to update an organization\n */\nexport function useUpdateOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [updateOrganization, { data, loading, error }] = useUpdateOrganizationMutation();\n\n return {\n updateOrganization: async (id: string, input: UpdateOrganizationInput) => {\n logger.info('mutation.start', {\n name: 'updateOrganization',\n organizationId: redactId(id),\n });\n try {\n const result = await updateOrganization({ variables: { id, input } });\n emit('organization.updated', { organizationId: id, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'updateOrganization',\n organizationId: redactId(id),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.update_failed', {\n organizationId: id,\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n {\n name: 'updateOrganization',\n organizationId: redactId(id),\n errorCode,\n errorMessage,\n },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n\n/**\n * Hook to delete an organization\n */\nexport function useDeleteOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [deleteOrganization, { data, loading, error }] = useDeleteOrganizationMutation();\n\n return {\n deleteOrganization: async (id: string) => {\n logger.info('mutation.start', {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n });\n try {\n const result = await deleteOrganization({ variables: { id } });\n emit('organization.deleted', { organizationId: id, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.delete_failed', {\n organizationId: id,\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n errorCode,\n errorMessage,\n },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useOrganizations.js","names":["apolloError","extractErrorCode","error","extractErrorMessage","useOrganizations","useGetOrganizationsQuery","enabled","data","limit","offset","useMyOrganizations","useGetMyOrganizationsQuery","useOrganization","useGetOrganizationQuery","id","organization","loading","useCreateOrganization","useOrganizationEventEmitter","createOrganization","useCreateOrganizationMutation","recaptchaToken","result","input","organizationId","emit","logger","redactId","mutationError","errorCode","errorMessage","useUpdateOrganization","updateOrganization","useUpdateOrganizationMutation","useDeleteOrganization","deleteOrganization","useDeleteOrganizationMutation"],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrganizations.ts"],"sourcesContent":["import {\n useGetOrganizationsQuery,\n useGetMyOrganizationsQuery,\n useGetOrganizationQuery,\n useGetOrganizationBySlugQuery,\n useGetOrganizationsByTenantQuery,\n useCreateOrganizationMutation,\n useUpdateOrganizationMutation,\n useDeleteOrganizationMutation,\n} from '../../../../generated/global-operations';\nimport type {\n Organization,\n OrganizationFilterInput,\n CreateOrganizationInput,\n UpdateOrganizationInput,\n} from '../types';\nimport { useOrganizationEventEmitter } from '../../../hooks/useOrganizationEventEmitter';\nimport { logger, redactId } from '../../../shared/logger';\nimport { executeRecaptcha } from '../../../shared/recaptcha';\n\ninterface GraphqlExtensions {\n code?: string;\n}\n\ninterface GraphqlError {\n message?: string;\n extensions?: GraphqlExtensions;\n}\n\ninterface ApolloLikeError {\n message?: string;\n graphQLErrors?: ReadonlyArray<GraphqlError>;\n}\n\nfunction extractErrorCode(error: unknown): string | undefined {\n const apolloError = error as ApolloLikeError | undefined;\n return apolloError?.graphQLErrors?.[0]?.extensions?.code;\n}\n\nfunction extractErrorMessage(error: unknown): string {\n if (error instanceof Error) return error.message;\n const apolloError = error as ApolloLikeError | undefined;\n return apolloError?.message ?? 'Unknown error';\n}\n\n/**\n * Hook to fetch organizations with filtering and pagination\n *\n * Calls the platform-wide `allOrganizations` admin query. For the end-user\n * \"my orgs\" view use `useMyOrganizations`.\n */\nexport function useOrganizations(\n filter?: OrganizationFilterInput,\n limit: number = 20,\n offset: number = 0,\n enabled: boolean = true\n) {\n const { data, loading, error, refetch, fetchMore } = useGetOrganizationsQuery({\n variables: { filter, limit, offset },\n skip: !enabled,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.allOrganizations?.items as Organization[]) || [],\n total: data?.allOrganizations?.total || 0,\n limit: data?.allOrganizations?.limit || limit,\n offset: data?.allOrganizations?.offset || offset,\n hasMore: data?.allOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to fetch organizations the current user is a member of (owned + added to).\n *\n * Backed by the `myOrganizations` query which uses @rbac(scopeType: \"self\") —\n * authenticated user only, no permission check, resolver filters by actorId.\n * Use this in product-app contexts where the user should see *their* orgs.\n *\n * Filter input is intentionally not supported — `myOrganizations` returns the\n * full membership list and any further filtering should be done client-side\n * (or via a future `myOrganizationsFiltered` query if the use-case warrants it).\n */\nexport function useMyOrganizations(\n limit: number = 20,\n offset: number = 0,\n enabled: boolean = true\n) {\n const { data, loading, error, refetch, fetchMore } = useGetMyOrganizationsQuery({\n variables: { limit, offset },\n skip: !enabled,\n // First mount: use cache if present, then revalidate from the network in\n // the background. Subsequent renders within the same session reuse the\n // cache. This keeps the list fresh when the user navigates back after\n // accepting an invite in another tab without paying the round-trip on\n // every re-render.\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.myOrganizations?.items as Organization[]) || [],\n total: data?.myOrganizations?.total || 0,\n limit: data?.myOrganizations?.limit || limit,\n offset: data?.myOrganizations?.offset || offset,\n hasMore: data?.myOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to fetch a single organization by ID\n */\nexport function useOrganization(id: string) {\n const { data, loading, error, refetch } = useGetOrganizationQuery({\n variables: { id },\n skip: !id,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n const organization = data?.organization as Organization | undefined;\n\n return {\n organization,\n loading: loading && !organization,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to fetch an organization by slug\n */\nexport function useOrganizationBySlug(tenantId: string, slug: string) {\n const { data, loading, error, refetch } = useGetOrganizationBySlugQuery({\n variables: { tenantId, slug },\n skip: !tenantId || !slug,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organization: data?.organizationBySlug as Organization | undefined,\n loading,\n error,\n refetch,\n };\n}\n\n/**\n * Hook to fetch organizations by tenant ID\n */\nexport function useOrganizationsByTenant(tenantId: string, limit: number = 20, offset: number = 0) {\n const { data, loading, error, refetch, fetchMore } = useGetOrganizationsByTenantQuery({\n variables: { tenantId, limit, offset },\n skip: !tenantId,\n fetchPolicy: 'cache-first',\n nextFetchPolicy: 'cache-first',\n });\n\n return {\n organizations: (data?.allOrganizations?.items as Organization[]) || [],\n total: data?.allOrganizations?.total || 0,\n limit: data?.allOrganizations?.limit || limit,\n offset: data?.allOrganizations?.offset || offset,\n hasMore: data?.allOrganizations?.hasMore || false,\n loading,\n error,\n refetch,\n fetchMore,\n };\n}\n\n/**\n * Hook to create a new organization\n */\nexport function useCreateOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [createOrganization, { data, loading, error }] = useCreateOrganizationMutation();\n\n return {\n createOrganization: async (input: CreateOrganizationInput) => {\n logger.info('mutation.start', { name: 'createOrganization' });\n try {\n const recaptchaToken = await executeRecaptcha('organization_create');\n const result = await createOrganization({\n variables: {\n input: {\n ...input,\n ...(recaptchaToken ? { recaptchaToken } : {}),\n },\n },\n });\n const organizationId = result.data?.createOrganization?.id;\n emit('organization.created', { organizationId, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'createOrganization',\n organizationId: redactId(organizationId),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.create_failed', {\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n { name: 'createOrganization', errorCode, errorMessage },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n\n/**\n * Hook to update an organization\n */\nexport function useUpdateOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [updateOrganization, { data, loading, error }] = useUpdateOrganizationMutation();\n\n return {\n updateOrganization: async (id: string, input: UpdateOrganizationInput) => {\n logger.info('mutation.start', {\n name: 'updateOrganization',\n organizationId: redactId(id),\n });\n try {\n const result = await updateOrganization({ variables: { id, input } });\n emit('organization.updated', { organizationId: id, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'updateOrganization',\n organizationId: redactId(id),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.update_failed', {\n organizationId: id,\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n {\n name: 'updateOrganization',\n organizationId: redactId(id),\n errorCode,\n errorMessage,\n },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n\n/**\n * Hook to delete an organization\n */\nexport function useDeleteOrganization() {\n const { emit } = useOrganizationEventEmitter();\n const [deleteOrganization, { data, loading, error }] = useDeleteOrganizationMutation();\n\n return {\n deleteOrganization: async (id: string) => {\n logger.info('mutation.start', {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n });\n try {\n const result = await deleteOrganization({ variables: { id } });\n emit('organization.deleted', { organizationId: id, source: 'microfe-organization' });\n logger.info('mutation.success', {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n });\n return result;\n } catch (mutationError) {\n const errorCode = extractErrorCode(mutationError);\n const errorMessage = extractErrorMessage(mutationError);\n emit('organization.delete_failed', {\n organizationId: id,\n source: 'microfe-organization',\n errorCode,\n errorMessage,\n });\n logger.error(\n 'mutation.failed',\n {\n name: 'deleteOrganization',\n organizationId: redactId(id),\n errorCode,\n errorMessage,\n },\n mutationError\n );\n throw mutationError;\n }\n },\n data,\n loading,\n error,\n };\n}\n"],"mappings":"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"}
|
|
@@ -3,61 +3,62 @@ import { ActorType as t } from "../../../../generated/global-types.js";
|
|
|
3
3
|
import { useEffect as n, useMemo as r, useState as i } from "react";
|
|
4
4
|
import { useAuth as a } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
5
5
|
import { useApolloClient as o } from "@apollo/client/react";
|
|
6
|
-
|
|
6
|
+
// src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts
|
|
7
7
|
var s = 50;
|
|
8
8
|
function c(c, l, u) {
|
|
9
|
-
let { user: d } = a(), f = d?.id ?? "", p = o(), m = r(() => [
|
|
9
|
+
let { user: d } = a(), f = d?.id ?? "", p = o(), m = r(() => c.join(""), [c]), h = r(() => [...c], [m]), g = r(() => h.map((e) => ({
|
|
10
10
|
actorId: f,
|
|
11
11
|
actorType: t.User,
|
|
12
12
|
scopeId: e,
|
|
13
13
|
resourceType: l,
|
|
14
14
|
action: u
|
|
15
15
|
})), [
|
|
16
|
-
|
|
16
|
+
h,
|
|
17
17
|
f,
|
|
18
18
|
l,
|
|
19
19
|
u
|
|
20
|
-
]),
|
|
20
|
+
]), _ = !f || h.length === 0, [v, y] = i([]), [b, x] = i(!1), [S, C] = i(!1);
|
|
21
21
|
return n(() => {
|
|
22
|
-
if (
|
|
23
|
-
|
|
22
|
+
if (_) {
|
|
23
|
+
y([]), x(!1), C(!1);
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
let t = !1;
|
|
27
|
-
|
|
27
|
+
x(!0), C(!1);
|
|
28
28
|
let n = [];
|
|
29
|
-
for (let e = 0; e <
|
|
29
|
+
for (let e = 0; e < g.length; e += s) n.push(g.slice(e, e + s));
|
|
30
30
|
return Promise.all(n.map((t) => p.query({
|
|
31
31
|
query: e,
|
|
32
32
|
variables: { inputs: t },
|
|
33
33
|
fetchPolicy: "cache-first"
|
|
34
34
|
}))).then((e) => {
|
|
35
|
-
|
|
35
|
+
if (t) return;
|
|
36
|
+
let n = e.flatMap((e) => e.data?.checkPermissions ?? []);
|
|
37
|
+
y(n), x(!1);
|
|
36
38
|
}).catch(() => {
|
|
37
|
-
t || (
|
|
39
|
+
t || (y([]), C(!0), x(!1));
|
|
38
40
|
}), () => {
|
|
39
41
|
t = !0;
|
|
40
42
|
};
|
|
41
43
|
}, [
|
|
42
44
|
p,
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
g,
|
|
46
|
+
_
|
|
45
47
|
]), {
|
|
46
48
|
allowedIds: r(() => {
|
|
47
|
-
if (
|
|
48
|
-
let e = /* @__PURE__ */ new Set(), t = Math.min(
|
|
49
|
-
for (let n = 0; n < t; n += 1)
|
|
49
|
+
if (_) return /* @__PURE__ */ new Set();
|
|
50
|
+
let e = /* @__PURE__ */ new Set(), t = Math.min(v.length, h.length);
|
|
51
|
+
for (let n = 0; n < t; n += 1) v[n].allowed && h[n] && e.add(h[n]);
|
|
50
52
|
return e;
|
|
51
53
|
}, [
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
v,
|
|
55
|
+
h,
|
|
56
|
+
_
|
|
55
57
|
]),
|
|
56
|
-
loading: !
|
|
57
|
-
isError: !
|
|
58
|
+
loading: !_ && b,
|
|
59
|
+
isError: !_ && S
|
|
58
60
|
};
|
|
59
61
|
}
|
|
60
|
-
//#endregion
|
|
61
62
|
export { c as useOrgsWithPermission };
|
|
62
63
|
|
|
63
64
|
//# sourceMappingURL=useOrgsWithPermission.js.map
|
package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useOrgsWithPermission.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n // react-doctor-disable-next-line react-doctor/exhaustive-deps\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,
|
|
1
|
+
{"version":3,"file":"useOrgsWithPermission.js","names":["CHECK_PERMISSIONS_BATCH_LIMIT","useOrgsWithPermission","useAuth","actorId","user","client","useApolloClient","stableKey","useMemo","orgIds","stableIds","inputs","ActorType","resourceType","action","skip","results","setResults","useState","loading","setLoading","hadError","setHadError","useEffect","cancelled","chunks","i","CheckRbacPermissionsDocument","chunk","merged","responses","r","out","bounded"],"sources":["../../../../../src/organization-management/modules/organizations/hooks/useOrgsWithPermission.ts"],"sourcesContent":["import { useEffect, useMemo, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport { useAuth } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n CheckRbacPermissionsDocument,\n type CheckRbacPermissionsQuery,\n type CheckRbacPermissionsQueryVariables,\n} from '../../../../generated/global-operations';\nimport { ActorType } from '../../../../generated/global-types';\n\ntype PermissionCheckResult = CheckRbacPermissionsQuery['checkPermissions'][number];\n\n// Matches the server-side `MAX_BULK_CHECK_SIZE` on `checkPermissions`.\n// Kept as a constant so the chunking intent is explicit at the call site.\nconst CHECK_PERMISSIONS_BATCH_LIMIT = 50;\n\n/**\n * Filter a set of organization ids down to those where the current user has\n * a specific RBAC permission. Bulk-checks against the rbac service so the\n * result tracks the authoritative evaluator — including overrides and any\n * future OPA policies — instead of client-side-duplicating the\n * role→permission mapping.\n *\n * The server caps a single bulk call at `CHECK_PERMISSIONS_BATCH_LIMIT`\n * inputs, so when a user has more orgs than that we chunk into multiple\n * parallel queries via `client.query()` and merge results positionally.\n * Most users will fit in one batch; the chunking is defensive for power\n * users / service accounts with many memberships (the WorkspaceForm\n * caller fetches up to 100 orgs, so two batches is realistic).\n *\n * Use this whenever a control surface needs to constrain itself to \"orgs\n * where I can do X\" rather than \"orgs I'm a member of\" — e.g. the org\n * picker on Create Workspace, Invite Member targets, role-assignment\n * dropdowns. The backend `@rbac` directives still enforce on submission;\n * this is purely UX (don't show options that will 403).\n */\nexport function useOrgsWithPermission(\n orgIds: readonly string[],\n resourceType: string,\n action: string\n): { allowedIds: Set<string>; loading: boolean; isError: boolean } {\n const { user } = useAuth();\n const actorId = user?.id ?? '';\n const client = useApolloClient();\n\n // `stableKey` lets memos depend on the *values* of orgIds, not the array\n // reference — callers passing a fresh array with the same ids won't bust\n // the query cache or trigger refetch loops.\n const stableKey = useMemo(() => orgIds.join(''), [orgIds]);\n // react-doctor-disable-next-line react-doctor/exhaustive-deps\n const stableIds = useMemo(() => [...orgIds], [stableKey]); // eslint-disable-line react-hooks/exhaustive-deps\n\n const inputs = useMemo(\n () =>\n stableIds.map((scopeId) => ({\n actorId,\n actorType: ActorType.User,\n scopeId,\n resourceType,\n action,\n })),\n [stableIds, actorId, resourceType, action]\n );\n\n const skip = !actorId || stableIds.length === 0;\n\n // Fan out the bulk check into <=BATCH_LIMIT-sized chunks, run them in\n // parallel via the Apollo client (so per-chunk caching still works), and\n // concat results in input order to preserve the positional contract.\n // We can't use multiple useQuery hooks here because the chunk count is\n // dynamic, hence the client.query + Promise.all pattern in an effect.\n const [results, setResults] = useState<PermissionCheckResult[]>([]);\n const [loading, setLoading] = useState(false);\n const [hadError, setHadError] = useState(false);\n\n useEffect(() => {\n if (skip) {\n setResults([]);\n setLoading(false);\n setHadError(false);\n return;\n }\n\n let cancelled = false;\n setLoading(true);\n setHadError(false);\n\n const chunks: (typeof inputs)[] = [];\n for (let i = 0; i < inputs.length; i += CHECK_PERMISSIONS_BATCH_LIMIT) {\n chunks.push(inputs.slice(i, i + CHECK_PERMISSIONS_BATCH_LIMIT));\n }\n\n Promise.all(\n chunks.map((chunk) =>\n client.query<CheckRbacPermissionsQuery, CheckRbacPermissionsQueryVariables>({\n query: CheckRbacPermissionsDocument,\n variables: { inputs: chunk },\n fetchPolicy: 'cache-first',\n })\n )\n )\n .then((responses) => {\n if (cancelled) return;\n const merged = responses.flatMap((r) => r.data?.checkPermissions ?? []);\n setResults(merged);\n setLoading(false);\n })\n .catch(() => {\n if (cancelled) return;\n setResults([]);\n setHadError(true);\n setLoading(false);\n });\n\n return () => {\n cancelled = true;\n };\n }, [client, inputs, skip]);\n\n const allowedIds = useMemo(() => {\n if (skip) return new Set<string>();\n const out = new Set<string>();\n // Length-guard the positional mapping so we never silently\n // mis-attribute a permission to the wrong org if the backend ever\n // truncates, reorders, or filters the response.\n const bounded = Math.min(results.length, stableIds.length);\n for (let i = 0; i < bounded; i += 1) {\n if (results[i].allowed && stableIds[i]) out.add(stableIds[i]);\n }\n return out;\n }, [results, stableIds, skip]);\n\n return {\n allowedIds,\n loading: !skip && loading,\n isError: !skip && hadError,\n };\n}\n"],"mappings":";;;;;;AAcA,IAAMA,IAAgC;AAsBtC,SAAgBC,EACd,GACA,GACA,GACiE;CACjE,IAAM,EAAE,YAASC,EAAQ,GACnBC,IAAUC,GAAM,MAAM,IACtBC,IAASC,EAAgB,GAKzBC,IAAYC,QAAcC,EAAO,KAAK,EAAE,GAAG,CAACA,CAAM,CAAC,GAEnDC,IAAYF,QAAc,CAAC,GAAGC,CAAM,GAAG,CAACF,CAAS,CAAC,GAElDI,IAASH,QAEXE,EAAU,KAAK,OAAa;EAC1B;EACA,WAAWE,EAAU;EACrB;EACA;EACA;CACF,EAAE,GACJ;EAACF;EAAWP;EAASU;EAAcC;CAAM,CAC3C,GAEMC,IAAO,CAACZ,KAAWO,EAAU,WAAW,GAOxC,CAACM,GAASC,KAAcC,EAAkC,CAAC,CAAC,GAC5D,CAACC,GAASC,KAAcF,EAAS,EAAK,GACtC,CAACG,GAAUC,KAAeJ,EAAS,EAAK;CA2D9C,OAzDAK,QAAgB;EACd,IAAIR,GAAM;GAGR,AAFAE,EAAW,CAAC,CAAC,GACbG,EAAW,EAAK,GAChBE,EAAY,EAAK;GACjB;EACF;EAEA,IAAIE,IAAY;EAEhB,AADAJ,EAAW,EAAI,GACfE,EAAY,EAAK;EAEjB,IAAMG,IAA4B,CAAC;EACnC,KAAK,IAAIC,IAAI,GAAGA,IAAIf,EAAO,QAAQ,KAAKX,GACtC,EAAO,KAAKW,EAAO,MAAMe,GAAGA,IAAI1B,CAA6B,CAAC;EAyBhE,OAtBA,QAAQ,IACNyB,EAAO,KAAK,MACVpB,EAAO,MAAqE;GAC1E,OAAOsB;GACP,WAAW,EAAE,QAAQC,EAAM;GAC3B,aAAa;EACf,CAAC,CACH,CACF,CAAC,CACE,MAAM,MAAc;GACnB,IAAIJ,GAAW;GACf,IAAMK,IAASC,EAAU,SAAS,MAAMC,EAAE,MAAM,oBAAoB,CAAC,CAAC;GAEtE,AADAd,EAAWY,CAAM,GACjBT,EAAW,EAAK;EAClB,CAAC,CAAC,CACD,YAAY;GACX,AAAII,MACJP,EAAW,CAAC,CAAC,GACbK,EAAY,EAAI,GAChBF,EAAW,EAAK;EAClB,CAAC,SAEU;GACX,IAAY;EACd;CACF,GAAG;EAACf;EAAQM;EAAQI;CAAI,CAAC,GAelB;EACL,YAdiBP,QAAc;GAC/B,IAAIO,GAAM,uBAAO,IAAI,IAAY;GACjC,IAAMiB,oBAAM,IAAI,IAAY,GAItBC,IAAU,KAAK,IAAIjB,EAAQ,QAAQN,EAAU,MAAM;GACzD,KAAK,IAAIgB,IAAI,GAAGA,IAAIO,GAAS,KAAK,GAChC,AAAIjB,EAAQU,EAAE,CAAC,WAAWhB,EAAUgB,MAAIM,EAAI,IAAItB,EAAUgB,EAAE;GAE9D,OAAOM;EACT,GAAG;GAAChB;GAASN;GAAWK;EAAI,CAG1B;EACA,SAAS,CAACA,KAAQI;EAClB,SAAS,CAACJ,KAAQM;CACpB;AACF"}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { toast as e } from "../../../../node_modules/sonner/dist/index.js";
|
|
2
|
+
import { useOrgConsole as t } from "../../../providers/OrgConsoleProvider.js";
|
|
3
3
|
import { useCreateOrganization as n } from "../hooks/useOrganizations.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
4
|
+
import { useTenants as r } from "../../workspaces/hooks/useWorkspaces.js";
|
|
5
|
+
import { OrganizationForm as i } from "../components/OrganizationForm.js";
|
|
6
|
+
import { LoadingState as a } from "../../../shared/components/feedback/LoadingState.js";
|
|
7
|
+
import { PERMISSIONS as o } from "../../../constants/permissions.js";
|
|
8
|
+
import { useTr as s } from "../../../shared/hooks/useTr.js";
|
|
9
|
+
import { useOrganizationPageView as c } from "../../../hooks/useOrganizationPageView.js";
|
|
10
10
|
import { ArrowLeft as l } from "lucide-react";
|
|
11
11
|
import { GlassCard as u, PagePurpose as d } from "@burdenoff/fe-libs/ui";
|
|
12
12
|
import { usePermissions as f } from "@burdenoff/fe-libs/shared/providers/shell";
|
|
13
13
|
import { jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
14
|
-
|
|
14
|
+
// src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx
|
|
15
15
|
function h() {
|
|
16
|
-
|
|
17
|
-
let h =
|
|
16
|
+
c("organizations.create");
|
|
17
|
+
let h = s(), { navigate: g, basePath: _ = "/organizations", tenantId: v } = t(), { hasPermission: y, isLoading: b } = f(), { createOrganization: x, loading: S } = n(), { tenants: C } = r(100, 0, !0), w = C.find((e) => e.type === "SHARED"), T = v ? C.find((e) => e.id === v) : w ?? (C.length >= 1 ? C[0] : void 0), E = C.length <= 1, D = async (t) => {
|
|
18
18
|
try {
|
|
19
|
-
let n = await x(
|
|
19
|
+
let n = await x(t);
|
|
20
20
|
if (n.errors?.length) throw Error(n.errors[0]?.message ?? h("organizations.create.failed", "Failed to create organization"));
|
|
21
|
-
n.data?.createOrganization && (
|
|
22
|
-
} catch (
|
|
23
|
-
|
|
21
|
+
n.data?.createOrganization && (e.success(h("organizations.create.success", "Organization created successfully")), g(`${_}/${n.data.createOrganization.id}`));
|
|
22
|
+
} catch (t) {
|
|
23
|
+
e.error(t instanceof Error ? t.message : h("organizations.create.failed", "Failed to create organization"));
|
|
24
24
|
}
|
|
25
25
|
}, O = () => {
|
|
26
26
|
g(_);
|
|
27
27
|
};
|
|
28
28
|
return b ? /* @__PURE__ */ p("div", {
|
|
29
29
|
className: "min-h-full bg-[var(--color-bg-canvas)] p-6",
|
|
30
|
-
children: /* @__PURE__ */ p(
|
|
31
|
-
}) : y(
|
|
30
|
+
children: /* @__PURE__ */ p(a, { message: h("common.loadingPermissions", "Loading permissions...") })
|
|
31
|
+
}) : y(o.ORG_CREATE) ? /* @__PURE__ */ m("div", {
|
|
32
32
|
className: "min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200",
|
|
33
33
|
children: [/* @__PURE__ */ m("div", {
|
|
34
34
|
className: "mb-6",
|
|
@@ -61,7 +61,7 @@ function h() {
|
|
|
61
61
|
treatment: "glass",
|
|
62
62
|
glow: !0,
|
|
63
63
|
className: "border-border-seam p-6",
|
|
64
|
-
children: /* @__PURE__ */ p(
|
|
64
|
+
children: /* @__PURE__ */ p(i, {
|
|
65
65
|
tenantId: v || T?.id,
|
|
66
66
|
tenantName: T?.name,
|
|
67
67
|
hideTenantSelector: E,
|
|
@@ -79,7 +79,6 @@ function h() {
|
|
|
79
79
|
})
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
|
-
//#endregion
|
|
83
82
|
export { h as OrganizationCreatePage };
|
|
84
83
|
|
|
85
84
|
//# sourceMappingURL=OrganizationCreatePage.js.map
|
package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n // Prefer an explicit context tenant; otherwise pick the shared tenant (or the\n // only one available). With a single shared tenant there is nothing to\n // choose, so we hide the tenant picker entirely and just submit it.\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const selectedTenant = tenantId\n ? tenants.find((tenant) => tenant.id === tenantId)\n : (sharedTenant ?? (tenants.length >= 1 ? tenants[0] : undefined));\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between (e.g. a dedicated tenant in addition to shared).\n const hideTenantSelector = tenants.length <= 1;\n\n const handleSubmit = async (data: CreateOrganizationInput | UpdateOrganizationInput) => {\n try {\n const createData = data as CreateOrganizationInput;\n const result = await createOrganization(createData);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n\n if (result.data?.createOrganization) {\n toast.success(tr('organizations.create.success', 'Organization created successfully'));\n navigate(`${basePath}/${result.data.createOrganization.id}`);\n }\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(basePath);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_CREATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.create.noPermission',\n \"You don't have permission to create organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200\">\n {/* Page Header */}\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('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.create.title', 'Create Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.create.subtitle', 'Add a new organization to a tenant')}\n </p>\n <PagePurpose className=\"mt-2 max-w-2xl\">\n {tr(\n 'organizations.create.purpose',\n 'An organization is the home for a company or team — once created, you can add workspaces under it, invite members, and manage everyone in one place. Give it a clear name and slug so people recognize it.'\n )}\n </PagePurpose>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-6\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n hideTenantSelector={hideTenantSelector}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,
|
|
1
|
+
{"version":3,"file":"OrganizationCreatePage.js","names":["createData","OrganizationCreatePage","tr","useTr","useOrgConsole","permissionsLoading","usePermissions","useCreateOrganization","useTenants","sharedTenant","tenants","tenant","selectedTenant","tenantId","hideTenantSelector","handleSubmit","result","createOrganization","data","toast","navigate","basePath","error","handleCancel","LoadingState","hasPermission","PERMISSIONS","ArrowLeft","PagePurpose","GlassCard","OrganizationForm"],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { GlassCard, PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n // Prefer an explicit context tenant; otherwise pick the shared tenant (or the\n // only one available). With a single shared tenant there is nothing to\n // choose, so we hide the tenant picker entirely and just submit it.\n const sharedTenant = tenants.find((tenant) => tenant.type === 'SHARED');\n const selectedTenant = tenantId\n ? tenants.find((tenant) => tenant.id === tenantId)\n : (sharedTenant ?? (tenants.length >= 1 ? tenants[0] : undefined));\n\n // Show the tenant picker only when the user genuinely has more than one\n // tenant to choose between (e.g. a dedicated tenant in addition to shared).\n const hideTenantSelector = tenants.length <= 1;\n\n const handleSubmit = async (data: CreateOrganizationInput | UpdateOrganizationInput) => {\n try {\n const createData = data as CreateOrganizationInput;\n const result = await createOrganization(createData);\n if (result.errors?.length) {\n throw new Error(\n result.errors[0]?.message ??\n tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n\n if (result.data?.createOrganization) {\n toast.success(tr('organizations.create.success', 'Organization created successfully'));\n navigate(`${basePath}/${result.data.createOrganization.id}`);\n }\n } catch (error) {\n toast.error(\n error instanceof Error\n ? error.message\n : tr('organizations.create.failed', 'Failed to create organization')\n );\n }\n };\n\n const handleCancel = () => {\n navigate(basePath);\n };\n\n if (permissionsLoading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <LoadingState message={tr('common.loadingPermissions', 'Loading permissions...')} />\n </div>\n );\n }\n\n if (!hasPermission(PERMISSIONS.ORG_CREATE)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr(\n 'organizations.create.noPermission',\n \"You don't have permission to create organizations.\"\n )}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-200\">\n {/* Page Header */}\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('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.create.title', 'Create Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.create.subtitle', 'Add a new organization to a tenant')}\n </p>\n <PagePurpose className=\"mt-2 max-w-2xl\">\n {tr(\n 'organizations.create.purpose',\n 'An organization is the home for a company or team — once created, you can add workspaces under it, invite members, and manage everyone in one place. Give it a clear name and slug so people recognize it.'\n )}\n </PagePurpose>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <GlassCard treatment=\"glass\" glow className=\"border-border-seam p-6\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n hideTenantSelector={hideTenantSelector}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </GlassCard>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;AAkBA,SAAgBC,IAAyB;CACvC,EAAwB,sBAAsB;CAC9C,IAAMC,IAAKC,EAAM,GACX,EAAE,aAAU,cAAW,kBAAkB,gBAAaC,EAAc,GACpE,EAAE,kBAAe,WAAWC,MAAuBC,EAAe,GAClE,EAAE,uBAAoB,eAAYC,EAAsB,GACxD,EAAE,eAAYC,EAAW,KAAK,GAAG,EAAI,GAKrCC,IAAeC,EAAQ,MAAM,MAAWC,EAAO,SAAS,QAAQ,GAChEC,IAAiBC,IACnBH,EAAQ,MAAM,MAAWC,EAAO,OAAOE,CAAQ,IAC9CJ,MAAiBC,EAAQ,UAAU,IAAIA,EAAQ,KAAK,KAAA,IAInDI,IAAqBJ,EAAQ,UAAU,GAEvCK,IAAe,OAAO,MAA4D;EACtF,IAAI;GAEF,IAAMC,IAAS,MAAMC,EAAmBjB,CAAU;GAClD,IAAIgB,EAAO,QAAQ,QACjB,MAAU,MACRA,EAAO,OAAO,EAAE,EAAE,WAChBd,EAAG,+BAA+B,+BAA+B,CACrE;GAGF,AAAIc,EAAO,MAAM,uBACfG,EAAM,QAAQjB,EAAG,gCAAgC,mCAAmC,CAAC,GACrFkB,EAAS,GAAGC,EAAS,GAAGL,EAAO,KAAK,mBAAmB,IAAI;EAE/D,SAASM,GAAO;GACd,EAAM,MACJA,aAAiB,QACbA,EAAM,UACNpB,EAAG,+BAA+B,+BAA+B,CACvE;EACF;CACF,GAEMqB,UAAqB;EACzB,EAASF,CAAQ;CACnB;CAuBA,OArBIhB,IAEA,kBAAC,OAAD;EAAK,WAAU;EACb,UAAA,kBAACmB,GAAD,EAAc,SAAStB,EAAG,6BAA6B,wBAAwB,EAAI,CAAA;CAChF,CAAA,IAIJuB,EAAcC,EAAY,UAAU,IAcvC,kBAAC,OAAD;EAAK,WAAU;EAAf,UAAA,CAEE,kBAAC,OAAD;GAAK,WAAU;GAAf,UAAA;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAASH;KACT,WAAU;KAHZ,UAAA,CAWE,kBAACI,GAAD,EAAW,MAAM,GAAK,CAAA,GACtB,kBAAC,QAAD;MAAM,WAAU;MACb,UAAAzB,EAAG,6BAA6B,uBAAuB;KACpD,CAAA,CACA;;IAER,kBAAC,MAAD;KAAI,WAAU;KACX,UAAAA,EAAG,8BAA8B,qBAAqB;IACrD,CAAA;IACJ,kBAAC,KAAD;KAAG,WAAU;KACV,UAAAA,EAAG,iCAAiC,oCAAoC;IACxE,CAAA;IACH,kBAAC0B,GAAD;KAAa,WAAU;KACpB,UAAA1B,EACC,gCACA,4MACF;IACW,CAAA;GACV;EAGL,CAAA,GAAA,kBAAC,OAAD;GAAK,WAAU;GACb,UAAA,kBAAC2B,GAAD;IAAW,WAAU;IAAQ,MAAA;IAAK,WAAU;IAC1C,UAAA,kBAACC,GAAD;KACE,UAAUjB,KAAYD,GAAgB;KACtC,YAAYA,GAAgB;KACR;KACpB,UAAUG;KACV,UAAUQ;KACD;IACV,CAAA;GACQ,CAAA;EACR,CAAA,CACF;C,KA3DH,kBAAC,OAAD;EAAK,WAAU;EACb,UAAA,kBAAC,OAAD;GAAK,WAAU;GACZ,UAAArB,EACC,qCACA,oDACF;EACG,CAAA;CACF,CAAA;AAsDX"}
|