@burdenoff/microfe-organization 2026.616.1 → 2026.624.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/organization-management/modules/members/components/BulkInviteForm.js +2 -2
  2. package/dist/organization-management/modules/members/components/BulkInviteForm.js.map +1 -1
  3. package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -2
  4. package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
  5. package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -2
  6. package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
  7. package/dist/organization-management/modules/members/pages/MemberDetailPage.js +1 -1
  8. package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
  9. package/dist/organization-management/modules/members/pages/MembersListPage.js +2 -2
  10. package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
  11. package/dist/organization-management/modules/members/pages/MyInvitesPage.js +2 -2
  12. package/dist/organization-management/modules/members/pages/MyInvitesPage.js.map +1 -1
  13. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js +5 -5
  14. package/dist/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.js.map +1 -1
  15. package/dist/organization-management/modules/organizations/components/OrganizationForm.js +2 -2
  16. package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
  17. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +1 -1
  18. package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
  19. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +3 -3
  20. package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
  21. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +1 -1
  22. package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
  23. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +3 -3
  24. package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
  25. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +3 -3
  26. package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
  27. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +2 -2
  28. package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
  29. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +1 -1
  30. package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
  31. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +3 -3
  32. package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
  33. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +1 -1
  34. package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
  35. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +2 -2
  36. package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
  37. package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -1
  38. package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
  39. package/dist/organization-management/shared/components/feedback/ErrorState.js +1 -1
  40. package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
  41. package/dist/organization-management/shared/components/input/SearchInput.js +1 -1
  42. package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
  43. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"MyInvitesPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MyInvitesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { NavLink } from 'react-router-dom';\nimport { Mail, Loader2, AlertCircle, BriefcaseBusiness, Building2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { useMyInvites, useAcceptInvite, useRejectInvite } from '../hooks/useInvites';\nimport {\n useAcceptWorkspaceInvite,\n useMyWorkspaceInvites,\n useRejectWorkspaceInvite,\n} from '../hooks/useWorkspaceInvites';\nimport { MyInviteCard } from '../components/MyInviteCard';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { InviteStatus as OrganizationInviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport {\n getProjectDisplayNames,\n getRoleDisplayNames,\n getWorkspaceDisplayName,\n} from '../utils/workspaceInvitationDisplay';\n\n/**\n * My Invites Page\n * Shows pending invites for the current user with accept/reject actions\n */\ntype MyInvitesTab = 'workspace' | 'organization';\n\ninterface MyInvitesPageProps {\n activeTab?: MyInvitesTab;\n}\n\ninterface TabActionError {\n tab: MyInvitesTab;\n message: string;\n}\n\ninterface InvitesTabNavProps {\n activeTab: MyInvitesTab;\n basePath: string;\n tr: ReturnType<typeof useTr>;\n}\n\nfunction formatDate(value?: string | null) {\n if (!value) return null;\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction joinPath(base: string, path: string): string {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n}\n\nfunction InvitesTabNav({ activeTab, basePath, tr }: InvitesTabNavProps) {\n const getTabClassName = (isActive: boolean) =>\n [\n 'border-b-2 px-4 py-2 text-sm font-medium transition-colors',\n isActive\n ? 'border-[var(--color-border-focus)] text-[var(--color-text-primary)]'\n : 'border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]',\n ].join(' ');\n\n return (\n <div className=\"mb-6 flex gap-2 border-b border-[var(--color-border-default)]\">\n <NavLink\n to={joinPath(basePath, '/invites')}\n end\n className={getTabClassName(activeTab === 'workspace')}\n >\n {tr('members.myInvites.workspaceTab', 'Workspace Invites')}\n </NavLink>\n <NavLink\n to={joinPath(basePath, '/invites/organizations')}\n className={getTabClassName(activeTab === 'organization')}\n >\n {tr('members.myInvites.organizationTab', 'Organization Invites')}\n </NavLink>\n </div>\n );\n}\n\nexport function MyInvitesPage({ activeTab = 'workspace' }: MyInvitesPageProps) {\n useOrganizationPageView(\n activeTab === 'workspace'\n ? 'organizations.my-workspace-invites'\n : 'organizations.my-organization-invites'\n );\n const tr = useTr();\n const { isLoading: permissionsLoading } = usePermissions();\n const { basePath = '/organizations', currentUser, authToken, apiGatewayUrl } = useOrgConsole();\n const orgInvitesEnabled = activeTab === 'organization';\n const workspaceInvitesEnabled = activeTab === 'workspace';\n const { invites, loading, error, total } = useMyInvites(\n OrganizationInviteStatus.Pending,\n 20,\n 0,\n orgInvitesEnabled\n );\n const {\n invites: workspaceInvites,\n loading: workspaceLoading,\n error: workspaceError,\n total: workspaceTotal,\n refetch: refetchWorkspaceInvites,\n } = useMyWorkspaceInvites(currentUser?.email, workspaceInvitesEnabled);\n const { acceptInvite, loading: accepting } = useAcceptInvite();\n const { rejectInvite, loading: rejecting } = useRejectInvite();\n const { acceptInvite: acceptWorkspaceInvite, loading: acceptingWorkspace } =\n useAcceptWorkspaceInvite();\n const { rejectInvite: rejectWorkspaceInvite, loading: rejectingWorkspace } =\n useRejectWorkspaceInvite();\n const [actionError, setActionError] = useState<TabActionError | null>(null);\n\n const visibleActionError = actionError?.tab === activeTab ? actionError.message : null;\n const [processingWorkspaceAction, setProcessingWorkspaceAction] = useState<{\n inviteId: string;\n action: 'accept' | 'reject';\n } | null>(null);\n\n const workspaceInviteActors = useMemo<ActorInput[]>(\n () =>\n workspaceInvites.flatMap((invite) =>\n invite.invitedById ? [{ actorId: invite.invitedById, actorType: 'user' as const }] : []\n ),\n [workspaceInvites]\n );\n\n const inviterProfiles: ActorProfileMap = useActorProfiles(workspaceInviteActors, {\n apiGatewayUrl,\n authToken,\n });\n\n const sortedWorkspaceInvites = useMemo(\n () =>\n [...workspaceInvites].sort(\n (left: (typeof workspaceInvites)[number], right: (typeof workspaceInvites)[number]) =>\n new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime()\n ),\n [workspaceInvites]\n );\n\n const totalCount = activeTab === 'workspace' ? workspaceTotal : total;\n\n const handleAccept = async (inviteId: string) => {\n try {\n setActionError(null);\n await acceptInvite(inviteId);\n } catch (err) {\n setActionError({\n tab: 'organization',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.acceptFailed', 'Failed to accept invite'),\n });\n }\n };\n\n const handleReject = async (inviteId: string) => {\n try {\n setActionError(null);\n await rejectInvite(inviteId);\n } catch (err) {\n setActionError({\n tab: 'organization',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.rejectFailed', 'Failed to reject invite'),\n });\n }\n };\n\n const handleAcceptWorkspace = async (inviteId: string) => {\n try {\n setProcessingWorkspaceAction({ inviteId, action: 'accept' });\n setActionError(null);\n await acceptWorkspaceInvite(inviteId);\n await refetchWorkspaceInvites().catch(() => undefined);\n } catch (err) {\n setActionError({\n tab: 'workspace',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.workspaceAcceptFailed', 'Failed to accept workspace invite'),\n });\n } finally {\n setProcessingWorkspaceAction((current) =>\n current?.inviteId === inviteId && current.action === 'accept' ? null : current\n );\n }\n };\n\n const handleRejectWorkspace = async (inviteId: string) => {\n try {\n setProcessingWorkspaceAction({ inviteId, action: 'reject' });\n setActionError(null);\n await rejectWorkspaceInvite(inviteId);\n await refetchWorkspaceInvites().catch(() => undefined);\n } catch (err) {\n setActionError({\n tab: 'workspace',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.workspaceRejectFailed', 'Failed to reject workspace invite'),\n });\n } finally {\n setProcessingWorkspaceAction((current) =>\n current?.inviteId === inviteId && current.action === 'reject' ? null : current\n );\n }\n };\n\n // Own invites are always accessible — no permission denial needed.\n // We still wait for permissions to load to ensure consistent UX.\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 (workspaceInvitesEnabled && !currentUser?.email) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-6 text-center\">\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('members.myInvites.signInWorkspace', 'Sign in to view your workspace invites.')}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (orgInvitesEnabled && loading && invites.length === 0) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"flex items-center justify-center min-h-[300px] rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={24} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (workspaceInvitesEnabled && workspaceLoading && workspaceInvites.length === 0) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"flex items-center justify-center min-h-[300px] rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={24} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (orgInvitesEnabled && error) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-6 text-center\">\n <p className=\"text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (workspaceInvitesEnabled && workspaceError) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-6 text-center\">\n <p className=\"text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.workspaceLoadFailed', 'Failed to load workspace invites')}:{' '}\n {workspaceError.message}\n </p>\n </div>\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\">\n <div className=\"max-w-4xl mx-auto\">\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-6\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n {totalCount > 0 && (\n <span className=\"px-2.5 py-0.5 rounded-full text-xs font-medium bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]\">\n {totalCount}\n </span>\n )}\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n {visibleActionError && (\n <div className=\"mb-4 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <AlertCircle size={16} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{visibleActionError}</p>\n </div>\n )}\n\n {workspaceInvitesEnabled ? (\n sortedWorkspaceInvites.length === 0 ? (\n <div className=\"text-center py-12 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <BriefcaseBusiness\n size={48}\n className=\"mx-auto mb-4 text-[var(--color-text-muted)] opacity-50\"\n />\n <h3 className=\"text-lg font-medium text-[var(--color-text-primary)] mb-1\">\n {tr('members.myInvites.workspaceEmptyTitle', 'No pending workspace invites')}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.myInvites.workspaceEmptyDescription',\n 'When someone invites you to a workspace, it will appear here.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {sortedWorkspaceInvites.map((invite) => {\n const workspaceName = getWorkspaceDisplayName(invite);\n const roleNames = getRoleDisplayNames(invite);\n const projectNames = getProjectDisplayNames(invite);\n const inviterProfile = invite.invitedById\n ? inviterProfiles[invite.invitedById]\n : undefined;\n const inviterLabel =\n inviterProfile?.displayName ||\n inviterProfile?.email ||\n tr('members.myInvites.invitedByFallback', 'Someone');\n const isProcessingWorkspaceAccept =\n processingWorkspaceAction?.inviteId === invite.id &&\n processingWorkspaceAction?.action === 'accept' &&\n acceptingWorkspace;\n const isProcessingWorkspaceReject =\n processingWorkspaceAction?.inviteId === invite.id &&\n processingWorkspaceAction?.action === 'reject' &&\n rejectingWorkspace;\n\n return (\n <div\n key={invite.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5\"\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-[var(--color-bg-subtle)] p-2\">\n <BriefcaseBusiness\n size={20}\n className=\"text-[var(--color-text-secondary)]\"\n />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] break-words\">\n {workspaceName}\n </h3>\n <p className=\"mt-1 text-sm text-[var(--color-text-secondary)] break-all\">\n {invite.email}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{invite.status}</Badge>\n {roleNames.map((roleName) => (\n <Badge key={`${invite.id}-${roleName}`} variant=\"outline\">\n {roleName}\n </Badge>\n ))}\n </div>\n\n <dl className=\"grid gap-3 text-sm text-[var(--color-text-secondary)] sm:grid-cols-2\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.invitedByLabel', 'Invited by')}\n </dt>\n <dd className=\"break-all\">\n {inviterProfile ? (\n <ActorIdentity\n profile={inviterProfile}\n variant=\"inline\"\n hideIdDrilldown\n />\n ) : (\n inviterLabel\n )}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.receivedLabel', 'Received')}\n </dt>\n <dd>{formatDate(invite.createdAt) ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.expiresLabel', 'Expires')}\n </dt>\n <dd>{formatDate(invite.expiresAt) ?? '—'}</dd>\n </div>\n {projectNames.length > 0 ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.projectsLabel', 'Projects')}\n </dt>\n <dd className=\"mt-1 flex flex-wrap gap-2\">\n {projectNames.map((projectName) => (\n <Badge key={`${invite.id}-${projectName}`} variant=\"outline\">\n {projectName}\n </Badge>\n ))}\n </dd>\n </div>\n ) : null}\n </dl>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2 shrink-0\">\n <button\n type=\"button\"\n onClick={() => void handleAcceptWorkspace(invite.id)}\n disabled={isProcessingWorkspaceAccept || isProcessingWorkspaceReject}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-status-success-bg)] px-4 py-2 text-sm font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessingWorkspaceAccept ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : null}\n {tr('members.myInvites.accept', 'Accept')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleRejectWorkspace(invite.id)}\n disabled={isProcessingWorkspaceAccept || isProcessingWorkspaceReject}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-[var(--color-border-default)] bg-transparent px-4 py-2 text-sm font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessingWorkspaceReject ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : null}\n {tr('members.myInvites.decline', 'Decline')}\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )\n ) : invites.length === 0 ? (\n <div className=\"text-center py-12 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <Building2\n size={48}\n className=\"mx-auto mb-4 text-[var(--color-text-muted)] opacity-50\"\n />\n <h3 className=\"text-lg font-medium text-[var(--color-text-primary)] mb-1\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.myInvites.emptyDescription',\n 'When someone invites you to an organization, it will appear here.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {invites.map((invite) => (\n <MyInviteCard\n key={invite.id}\n invite={invite}\n onAccept={handleAccept}\n onReject={handleReject}\n accepting={accepting}\n rejecting={rejecting}\n />\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"MyInvitesPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MyInvitesPage.tsx"],"sourcesContent":["import { useMemo, useState } from 'react';\nimport { NavLink } from 'react-router-dom';\nimport { Mail, Loader2, AlertCircle, BriefcaseBusiness, Building2 } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport {\n ActorIdentity,\n useActorProfiles,\n type ActorInput,\n type ActorProfileMap,\n} from '@burdenoff/fe-libs/shared';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { useMyInvites, useAcceptInvite, useRejectInvite } from '../hooks/useInvites';\nimport {\n useAcceptWorkspaceInvite,\n useMyWorkspaceInvites,\n useRejectWorkspaceInvite,\n} from '../hooks/useWorkspaceInvites';\nimport { MyInviteCard } from '../components/MyInviteCard';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { InviteStatus as OrganizationInviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport {\n getProjectDisplayNames,\n getRoleDisplayNames,\n getWorkspaceDisplayName,\n} from '../utils/workspaceInvitationDisplay';\n\n/**\n * My Invites Page\n * Shows pending invites for the current user with accept/reject actions\n */\ntype MyInvitesTab = 'workspace' | 'organization';\n\ninterface MyInvitesPageProps {\n activeTab?: MyInvitesTab;\n}\n\ninterface TabActionError {\n tab: MyInvitesTab;\n message: string;\n}\n\ninterface InvitesTabNavProps {\n activeTab: MyInvitesTab;\n basePath: string;\n tr: ReturnType<typeof useTr>;\n}\n\nfunction formatDate(value?: string | null) {\n if (!value) return null;\n\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n\n return date.toLocaleDateString(undefined, {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction joinPath(base: string, path: string): string {\n const cleanBase = base.endsWith('/') ? base.slice(0, -1) : base;\n const cleanPath = path.startsWith('/') ? path : `/${path}`;\n return cleanBase + cleanPath;\n}\n\nfunction InvitesTabNav({ activeTab, basePath, tr }: InvitesTabNavProps) {\n const getTabClassName = (isActive: boolean) =>\n [\n 'border-b-2 px-4 py-2 text-sm font-medium transition-colors',\n isActive\n ? 'border-[var(--color-border-focus)] text-[var(--color-text-primary)]'\n : 'border-transparent text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)]',\n ].join(' ');\n\n return (\n <div className=\"mb-6 flex gap-2 border-b border-[var(--color-border-default)]\">\n <NavLink\n to={joinPath(basePath, '/invites')}\n end\n className={getTabClassName(activeTab === 'workspace')}\n >\n {tr('members.myInvites.workspaceTab', 'Workspace Invites')}\n </NavLink>\n <NavLink\n to={joinPath(basePath, '/invites/organizations')}\n className={getTabClassName(activeTab === 'organization')}\n >\n {tr('members.myInvites.organizationTab', 'Organization Invites')}\n </NavLink>\n </div>\n );\n}\n\nexport function MyInvitesPage({ activeTab = 'workspace' }: MyInvitesPageProps) {\n useOrganizationPageView(\n activeTab === 'workspace'\n ? 'organizations.my-workspace-invites'\n : 'organizations.my-organization-invites'\n );\n const tr = useTr();\n const { isLoading: permissionsLoading } = usePermissions();\n const { basePath = '/organizations', currentUser, authToken, apiGatewayUrl } = useOrgConsole();\n const orgInvitesEnabled = activeTab === 'organization';\n const workspaceInvitesEnabled = activeTab === 'workspace';\n const { invites, loading, error, total } = useMyInvites(\n OrganizationInviteStatus.Pending,\n 20,\n 0,\n orgInvitesEnabled\n );\n const {\n invites: workspaceInvites,\n loading: workspaceLoading,\n error: workspaceError,\n total: workspaceTotal,\n refetch: refetchWorkspaceInvites,\n } = useMyWorkspaceInvites(currentUser?.email, workspaceInvitesEnabled);\n const { acceptInvite, loading: accepting } = useAcceptInvite();\n const { rejectInvite, loading: rejecting } = useRejectInvite();\n const { acceptInvite: acceptWorkspaceInvite, loading: acceptingWorkspace } =\n useAcceptWorkspaceInvite();\n const { rejectInvite: rejectWorkspaceInvite, loading: rejectingWorkspace } =\n useRejectWorkspaceInvite();\n const [actionError, setActionError] = useState<TabActionError | null>(null);\n\n const visibleActionError = actionError?.tab === activeTab ? actionError.message : null;\n const [processingWorkspaceAction, setProcessingWorkspaceAction] = useState<{\n inviteId: string;\n action: 'accept' | 'reject';\n } | null>(null);\n\n const workspaceInviteActors = useMemo<ActorInput[]>(\n () =>\n workspaceInvites.flatMap((invite) =>\n invite.invitedById ? [{ actorId: invite.invitedById, actorType: 'user' as const }] : []\n ),\n [workspaceInvites]\n );\n\n const inviterProfiles: ActorProfileMap = useActorProfiles(workspaceInviteActors, {\n apiGatewayUrl,\n authToken,\n });\n\n const sortedWorkspaceInvites = useMemo(\n () =>\n [...workspaceInvites].sort(\n (left: (typeof workspaceInvites)[number], right: (typeof workspaceInvites)[number]) =>\n new Date(right.createdAt).getTime() - new Date(left.createdAt).getTime()\n ),\n [workspaceInvites]\n );\n\n const totalCount = activeTab === 'workspace' ? workspaceTotal : total;\n\n const handleAccept = async (inviteId: string) => {\n try {\n setActionError(null);\n await acceptInvite(inviteId);\n } catch (err) {\n setActionError({\n tab: 'organization',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.acceptFailed', 'Failed to accept invite'),\n });\n }\n };\n\n const handleReject = async (inviteId: string) => {\n try {\n setActionError(null);\n await rejectInvite(inviteId);\n } catch (err) {\n setActionError({\n tab: 'organization',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.rejectFailed', 'Failed to reject invite'),\n });\n }\n };\n\n const handleAcceptWorkspace = async (inviteId: string) => {\n try {\n setProcessingWorkspaceAction({ inviteId, action: 'accept' });\n setActionError(null);\n await acceptWorkspaceInvite(inviteId);\n await refetchWorkspaceInvites().catch(() => undefined);\n } catch (err) {\n setActionError({\n tab: 'workspace',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.workspaceAcceptFailed', 'Failed to accept workspace invite'),\n });\n } finally {\n setProcessingWorkspaceAction((current) =>\n current?.inviteId === inviteId && current.action === 'accept' ? null : current\n );\n }\n };\n\n const handleRejectWorkspace = async (inviteId: string) => {\n try {\n setProcessingWorkspaceAction({ inviteId, action: 'reject' });\n setActionError(null);\n await rejectWorkspaceInvite(inviteId);\n await refetchWorkspaceInvites().catch(() => undefined);\n } catch (err) {\n setActionError({\n tab: 'workspace',\n message:\n err instanceof Error\n ? err.message\n : tr('members.myInvites.workspaceRejectFailed', 'Failed to reject workspace invite'),\n });\n } finally {\n setProcessingWorkspaceAction((current) =>\n current?.inviteId === inviteId && current.action === 'reject' ? null : current\n );\n }\n };\n\n // Own invites are always accessible — no permission denial needed.\n // We still wait for permissions to load to ensure consistent UX.\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 (workspaceInvitesEnabled && !currentUser?.email) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-6 text-center\">\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('members.myInvites.signInWorkspace', 'Sign in to view your workspace invites.')}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (orgInvitesEnabled && loading && invites.length === 0) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"flex items-center justify-center min-h-[300px] rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={24} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (workspaceInvitesEnabled && workspaceLoading && workspaceInvites.length === 0) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"flex items-center justify-center min-h-[300px] rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={24} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </div>\n </div>\n );\n }\n\n if (orgInvitesEnabled && error) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-6 text-center\">\n <p className=\"text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n </div>\n </div>\n </div>\n );\n }\n\n if (workspaceInvitesEnabled && workspaceError) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <div className=\"max-w-4xl mx-auto\">\n <div className=\"mb-6 flex items-center gap-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n <div className=\"rounded-xl border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-6 text-center\">\n <p className=\"text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.workspaceLoadFailed', 'Failed to load workspace invites')}:{' '}\n {workspaceError.message}\n </p>\n </div>\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\">\n <div className=\"max-w-4xl mx-auto\">\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-6\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.myInvites.title', 'My Invites')}\n </h1>\n {totalCount > 0 && (\n <span className=\"px-2.5 py-0.5 rounded-full text-xs font-medium bg-[var(--color-action-primary-bg)] text-[var(--color-action-primary-text)]\">\n {totalCount}\n </span>\n )}\n </div>\n\n <InvitesTabNav activeTab={activeTab} basePath={basePath} tr={tr} />\n\n {visibleActionError && (\n <div className=\"mb-4 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <AlertCircle size={16} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{visibleActionError}</p>\n </div>\n )}\n\n {workspaceInvitesEnabled ? (\n sortedWorkspaceInvites.length === 0 ? (\n <div className=\"text-center py-12 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <BriefcaseBusiness\n size={48}\n className=\"mx-auto mb-4 text-[var(--color-text-muted)] opacity-50\"\n />\n <h3 className=\"text-lg font-medium text-[var(--color-text-primary)] mb-1\">\n {tr('members.myInvites.workspaceEmptyTitle', 'No pending workspace invites')}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.myInvites.workspaceEmptyDescription',\n 'When someone invites you to a workspace, it will appear here.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {sortedWorkspaceInvites.map((invite) => {\n const workspaceName = getWorkspaceDisplayName(invite);\n const roleNames = getRoleDisplayNames(invite);\n const projectNames = getProjectDisplayNames(invite);\n const inviterProfile = invite.invitedById\n ? inviterProfiles[invite.invitedById]\n : undefined;\n const inviterLabel =\n inviterProfile?.displayName ||\n inviterProfile?.email ||\n tr('members.myInvites.invitedByFallback', 'Someone');\n const isProcessingWorkspaceAccept =\n processingWorkspaceAction?.inviteId === invite.id &&\n processingWorkspaceAction?.action === 'accept' &&\n acceptingWorkspace;\n const isProcessingWorkspaceReject =\n processingWorkspaceAction?.inviteId === invite.id &&\n processingWorkspaceAction?.action === 'reject' &&\n rejectingWorkspace;\n\n return (\n <div\n key={invite.id}\n className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5\"\n >\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"min-w-0 space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-[var(--color-bg-subtle)] p-2\">\n <BriefcaseBusiness\n size={20}\n className=\"text-[var(--color-text-secondary)]\"\n />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] break-words\">\n {workspaceName}\n </h3>\n <p className=\"mt-1 text-sm text-[var(--color-text-secondary)] break-all\">\n {invite.email}\n </p>\n </div>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <Badge variant=\"secondary\">{invite.status}</Badge>\n {roleNames.map((roleName) => (\n <Badge key={`${invite.id}-${roleName}`} variant=\"outline\">\n {roleName}\n </Badge>\n ))}\n </div>\n\n <dl className=\"grid gap-3 text-sm text-[var(--color-text-secondary)] sm:grid-cols-2\">\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.invitedByLabel', 'Invited by')}\n </dt>\n <dd className=\"break-all\">\n {inviterProfile ? (\n <ActorIdentity\n profile={inviterProfile}\n variant=\"inline\"\n hideIdDrilldown\n />\n ) : (\n inviterLabel\n )}\n </dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.receivedLabel', 'Received')}\n </dt>\n <dd>{formatDate(invite.createdAt) ?? '—'}</dd>\n </div>\n <div>\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.expiresLabel', 'Expires')}\n </dt>\n <dd>{formatDate(invite.expiresAt) ?? '—'}</dd>\n </div>\n {projectNames.length > 0 ? (\n <div className=\"sm:col-span-2\">\n <dt className=\"font-medium text-[var(--color-text-primary)]\">\n {tr('members.myInvites.projectsLabel', 'Projects')}\n </dt>\n <dd className=\"mt-1 flex flex-wrap gap-2\">\n {projectNames.map((projectName) => (\n <Badge key={`${invite.id}-${projectName}`} variant=\"outline\">\n {projectName}\n </Badge>\n ))}\n </dd>\n </div>\n ) : null}\n </dl>\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2 shrink-0\">\n <button\n type=\"button\"\n onClick={() => void handleAcceptWorkspace(invite.id)}\n disabled={isProcessingWorkspaceAccept || isProcessingWorkspaceReject}\n className=\"inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-status-success-bg)] px-4 py-2 text-sm font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessingWorkspaceAccept ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : null}\n {tr('members.myInvites.accept', 'Accept')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleRejectWorkspace(invite.id)}\n disabled={isProcessingWorkspaceAccept || isProcessingWorkspaceReject}\n className=\"inline-flex items-center gap-1.5 rounded-lg border border-[var(--color-border-default)] bg-transparent px-4 py-2 text-sm font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {isProcessingWorkspaceReject ? (\n <Loader2 size={14} className=\"animate-spin\" />\n ) : null}\n {tr('members.myInvites.decline', 'Decline')}\n </button>\n </div>\n </div>\n </div>\n );\n })}\n </div>\n )\n ) : invites.length === 0 ? (\n <div className=\"text-center py-12 bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)]\">\n <Building2\n size={48}\n className=\"mx-auto mb-4 text-[var(--color-text-muted)] opacity-50\"\n />\n <h3 className=\"text-lg font-medium text-[var(--color-text-primary)] mb-1\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </h3>\n <p className=\"text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.myInvites.emptyDescription',\n 'When someone invites you to an organization, it will appear here.'\n )}\n </p>\n </div>\n ) : (\n <div className=\"space-y-3\">\n {invites.map((invite) => (\n <MyInviteCard\n key={invite.id}\n invite={invite}\n onAccept={handleAccept}\n onReject={handleReject}\n accepting={accepting}\n rejecting={rejecting}\n />\n ))}\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -52,7 +52,7 @@ function C({ icon: e, iconClass: t, title: n, description: r, email: i, primaryL
52
52
  /* @__PURE__ */ y("button", {
53
53
  type: "button",
54
54
  onClick: o,
55
- className: "mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
55
+ className: "mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90",
56
56
  children: [/* @__PURE__ */ v(p, { size: 16 }), a]
57
57
  })
58
58
  ]
@@ -106,12 +106,12 @@ function w() {
106
106
  className: "flex flex-col gap-3 sm:flex-row",
107
107
  children: [/* @__PURE__ */ v("a", {
108
108
  href: `/auth/login?returnTo=${e}`,
109
- className: "inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
109
+ className: "inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90",
110
110
  children: w("members.inviteAcceptance.signInButton", "Sign In")
111
111
  }), /* @__PURE__ */ y("button", {
112
112
  type: "button",
113
113
  onClick: () => D(E),
114
- className: "inline-flex items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98]",
114
+ className: "inline-flex items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]",
115
115
  children: [/* @__PURE__ */ v(p, { size: 16 }), w("members.inviteAcceptance.backToOrganizations", "Back to Organizations")]
116
116
  })]
117
117
  })
@@ -262,7 +262,7 @@ function w() {
262
262
  type: "button",
263
263
  onClick: () => void H(),
264
264
  disabled: J,
265
- className: "inline-flex flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--color-status-success-bg)] px-6 py-3 font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50",
265
+ className: "inline-flex flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--color-status-success-bg)] px-6 py-3 font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50",
266
266
  children: [L === "accept" ? /* @__PURE__ */ v(m, {
267
267
  size: 16,
268
268
  className: "animate-spin"
@@ -271,7 +271,7 @@ function w() {
271
271
  type: "button",
272
272
  onClick: () => void U(),
273
273
  disabled: J,
274
- className: "inline-flex flex-1 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-3 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50",
274
+ className: "inline-flex flex-1 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-3 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] disabled:cursor-not-allowed disabled:opacity-50",
275
275
  children: [L === "reject" ? /* @__PURE__ */ v(m, {
276
276
  size: 16,
277
277
  className: "animate-spin"
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationInviteAcceptancePage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Mail,\n Loader2,\n CheckCircle,\n XCircle,\n Clock,\n AlertTriangle,\n Ban,\n Home,\n Building2,\n} from 'lucide-react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { useOrganizationInvite, useAcceptInvite, useRejectInvite } from '../hooks/useInvites';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\n\n/**\n * OrganizationInviteAcceptancePage\n *\n * Per-invite acceptance page reached from the organization invite email link\n * (`/organizations/invite/:inviteId`). Mirrors the workspace invite-acceptance\n * flow: loads the single invite by id, renders the appropriate state machine\n * (PENDING → accept/reject, ACCEPTED/REJECTED/EXPIRED/CANCELLED), and gates the\n * unauthenticated case behind a sign-in CTA that returns the user here.\n *\n * The previous behaviour dropped the recipient on `/organizations/invites`\n * (the workspace-invites list tab) with no invite context, which rendered as\n * an empty page. The email now deep-links here with the invite id.\n */\nfunction formatDate(value?: string | null): string | null {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n return date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction PageShell({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-10 sm:px-6\">\n <div className=\"mx-auto max-w-2xl\">{children}</div>\n </div>\n );\n}\n\ninterface StatusViewProps {\n icon: typeof CheckCircle;\n iconClass: string;\n title: string;\n description: string;\n email?: string;\n primaryLabel: string;\n onPrimary: () => void;\n}\n\nfunction StatusView({\n icon: Icon,\n iconClass,\n title,\n description,\n email,\n primaryLabel,\n onPrimary,\n}: StatusViewProps) {\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8 text-center\">\n <div\n className={`mx-auto mb-4 flex size-14 items-center justify-center rounded-2xl ${iconClass}`}\n >\n <Icon size={28} />\n </div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">{title}</h1>\n <p className=\"mt-2 text-[var(--color-text-secondary)]\">{description}</p>\n {email ? (\n <p className=\"mt-1 break-all text-sm text-[var(--color-text-muted)]\">{email}</p>\n ) : null}\n <button\n type=\"button\"\n onClick={onPrimary}\n className=\"mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n <Home size={16} />\n {primaryLabel}\n </button>\n </div>\n </PageShell>\n );\n}\n\nexport function OrganizationInviteAcceptancePage() {\n useOrganizationPageView('organizations.invite-acceptance');\n const tr = useTr();\n const { inviteId = '' } = useParams<{ inviteId: string }>();\n const { basePath = '/organizations', navigate, currentUser, authToken } = useOrgConsole();\n\n const isAuthenticated = Boolean(authToken || currentUser?.email);\n const { invite, loading, error, refetch } = useOrganizationInvite(inviteId, isAuthenticated);\n const { acceptInvite } = useAcceptInvite();\n const { rejectInvite } = useRejectInvite();\n\n const [processing, setProcessing] = useState<'accept' | 'reject' | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const goToInvitesList = () => navigate(`${basePath}/invites/organizations`);\n\n const handleAccept = async () => {\n if (!inviteId) return;\n try {\n setProcessing('accept');\n setActionError(null);\n await acceptInvite(inviteId);\n navigate(invite?.organizationId ? `${basePath}/${invite.organizationId}` : basePath);\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr('members.inviteAcceptance.acceptFailed', 'Failed to accept invitation')\n );\n } finally {\n setProcessing(null);\n }\n };\n\n const handleReject = async () => {\n if (!inviteId) return;\n try {\n setProcessing('reject');\n setActionError(null);\n await rejectInvite(inviteId);\n goToInvitesList();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr('members.inviteAcceptance.rejectFailed', 'Failed to reject invitation')\n );\n } finally {\n setProcessing(null);\n }\n };\n\n // Unauthenticated — invitees must sign in with the invited email first.\n if (!isAuthenticated) {\n const returnTo = encodeURIComponent(`${basePath}/invite/${inviteId}`);\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8\">\n <div className=\"mb-4 flex items-center gap-3\">\n <div className=\"rounded-xl bg-[var(--color-bg-subtle)] p-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.inviteAcceptance.signInRequired', 'Sign in to view your invitation')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'members.inviteAcceptance.signInDescription',\n 'Organization invitations require an authenticated account before you can review or accept them.'\n )}\n </p>\n </div>\n </div>\n <p className=\"mb-6 text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.inviteAcceptance.signInHint',\n 'Use the same email address that received the invitation, then come back here to accept or decline it.'\n )}\n </p>\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <a\n href={`/auth/login?returnTo=${returnTo}`}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]\"\n >\n {tr('members.inviteAcceptance.signInButton', 'Sign In')}\n </a>\n <button\n type=\"button\"\n onClick={() => navigate(basePath)}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)] active:scale-[0.98]\"\n >\n <Home size={16} />\n {tr('members.inviteAcceptance.backToOrganizations', 'Back to Organizations')}\n </button>\n </div>\n </div>\n </PageShell>\n );\n }\n\n if (loading) {\n return (\n <PageShell>\n <div className=\"flex min-h-[300px] items-center justify-center rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={28} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </PageShell>\n );\n }\n\n if (error) {\n return (\n <StatusView\n icon={AlertTriangle}\n iconClass=\"bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]\"\n title={tr('members.inviteAcceptance.errorTitle', 'Error Loading Invitation')}\n description={error.message}\n primaryLabel={tr('members.inviteAcceptance.tryAgain', 'Try Again')}\n onPrimary={() => {\n void refetch();\n }}\n />\n );\n }\n\n if (!invite) {\n return (\n <StatusView\n icon={AlertTriangle}\n iconClass=\"bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)]\"\n title={tr('members.inviteAcceptance.notFoundTitle', 'Invitation Not Found')}\n description={tr(\n 'members.inviteAcceptance.notFoundDescription',\n 'This invitation link is invalid, has been removed, or was addressed to a different account.'\n )}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n const isExpiredByDate = invite.expiresAt && new Date(invite.expiresAt) < new Date();\n const effectiveStatus =\n invite.status === InviteStatus.Pending && isExpiredByDate\n ? InviteStatus.Expired\n : invite.status;\n\n if (effectiveStatus === InviteStatus.Accepted) {\n return (\n <StatusView\n icon={CheckCircle}\n iconClass=\"bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)]\"\n title={tr('members.inviteAcceptance.alreadyAcceptedTitle', 'Invitation Already Accepted')}\n description={tr(\n 'members.inviteAcceptance.alreadyAcceptedDescription',\n 'You have already accepted this invitation.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganization', 'Go to Organization')}\n onPrimary={() => navigate(`${basePath}/${invite.organizationId}`)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Rejected) {\n return (\n <StatusView\n icon={XCircle}\n iconClass=\"bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)]\"\n title={tr('members.inviteAcceptance.rejectedTitle', 'Invitation Declined')}\n description={tr(\n 'members.inviteAcceptance.rejectedDescription',\n 'This invitation was previously declined.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Expired) {\n return (\n <StatusView\n icon={Clock}\n iconClass=\"bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)]\"\n title={tr('members.inviteAcceptance.expiredTitle', 'Invitation Expired')}\n description={tr(\n 'members.inviteAcceptance.expiredDescription',\n 'This invitation has expired. Please contact an organization admin for a new one.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Cancelled) {\n return (\n <StatusView\n icon={Ban}\n iconClass=\"bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]\"\n title={tr('members.inviteAcceptance.cancelledTitle', 'Invitation Cancelled')}\n description={tr(\n 'members.inviteAcceptance.cancelledDescription',\n 'This invitation was cancelled by the sender and is no longer valid.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n // PENDING — show the acceptance UI.\n const organizationName = invite.organization?.name ?? invite.organizationId;\n const expiresLabel = formatDate(invite.expiresAt);\n const busy = processing !== null;\n\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"rounded-xl bg-[var(--color-bg-subtle)] p-3\">\n <Building2 size={24} className=\"text-[var(--color-text-primary)]\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.inviteAcceptance.pageTitle', 'Organization Invitation')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('members.inviteAcceptance.pageSubtitle', \"You've been invited to join\")}{' '}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {organizationName}\n </span>\n </p>\n </div>\n </div>\n\n <dl className=\"space-y-4 rounded-xl bg-[var(--color-bg-subtle)] p-5\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.emailLabel', 'Email Address')}\n </dt>\n <dd className=\"mt-1 break-all text-[var(--color-text-primary)]\">{invite.email}</dd>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.roleLabel', 'Role')}\n </dt>\n <dd>\n <Badge variant=\"secondary\">{invite.role}</Badge>\n </dd>\n </div>\n {expiresLabel ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.expiresLabel', 'Expires')}\n </dt>\n <dd className=\"mt-1 flex items-center gap-2 text-[var(--color-text-primary)]\">\n <Clock size={16} className=\"text-[var(--color-text-muted)]\" />\n {expiresLabel}\n </dd>\n </div>\n ) : null}\n {invite.message ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.messageLabel', 'Message')}\n </dt>\n <dd className=\"mt-1 text-[var(--color-text-secondary)]\">{invite.message}</dd>\n </div>\n ) : null}\n </dl>\n\n {actionError ? (\n <div className=\"mt-4 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <AlertTriangle size={16} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{actionError}</p>\n </div>\n ) : null}\n\n <div className=\"mt-6 flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-6 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() => void handleAccept()}\n disabled={busy}\n className=\"inline-flex flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--color-status-success-bg)] px-6 py-3 font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {processing === 'accept' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={16} />\n )}\n {tr('members.inviteAcceptance.acceptButton', 'Accept Invitation')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleReject()}\n disabled={busy}\n className=\"inline-flex flex-1 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-3 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] active:scale-[0.98] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {processing === 'reject' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <XCircle size={16} />\n )}\n {tr('members.inviteAcceptance.declineButton', 'Decline')}\n </button>\n </div>\n </div>\n </PageShell>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"OrganizationInviteAcceptancePage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/OrganizationInviteAcceptancePage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n Mail,\n Loader2,\n CheckCircle,\n XCircle,\n Clock,\n AlertTriangle,\n Ban,\n Home,\n Building2,\n} from 'lucide-react';\nimport { Badge } from '@burdenoff/fe-libs/ui';\nimport { useOrganizationInvite, useAcceptInvite, useRejectInvite } from '../hooks/useInvites';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\n\n/**\n * OrganizationInviteAcceptancePage\n *\n * Per-invite acceptance page reached from the organization invite email link\n * (`/organizations/invite/:inviteId`). Mirrors the workspace invite-acceptance\n * flow: loads the single invite by id, renders the appropriate state machine\n * (PENDING → accept/reject, ACCEPTED/REJECTED/EXPIRED/CANCELLED), and gates the\n * unauthenticated case behind a sign-in CTA that returns the user here.\n *\n * The previous behaviour dropped the recipient on `/organizations/invites`\n * (the workspace-invites list tab) with no invite context, which rendered as\n * an empty page. The email now deep-links here with the invite id.\n */\nfunction formatDate(value?: string | null): string | null {\n if (!value) return null;\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return null;\n return date.toLocaleDateString('en-US', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n hour: '2-digit',\n minute: '2-digit',\n });\n}\n\nfunction PageShell({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-10 sm:px-6\">\n <div className=\"mx-auto max-w-2xl\">{children}</div>\n </div>\n );\n}\n\ninterface StatusViewProps {\n icon: typeof CheckCircle;\n iconClass: string;\n title: string;\n description: string;\n email?: string;\n primaryLabel: string;\n onPrimary: () => void;\n}\n\nfunction StatusView({\n icon: Icon,\n iconClass,\n title,\n description,\n email,\n primaryLabel,\n onPrimary,\n}: StatusViewProps) {\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8 text-center\">\n <div\n className={`mx-auto mb-4 flex size-14 items-center justify-center rounded-2xl ${iconClass}`}\n >\n <Icon size={28} />\n </div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">{title}</h1>\n <p className=\"mt-2 text-[var(--color-text-secondary)]\">{description}</p>\n {email ? (\n <p className=\"mt-1 break-all text-sm text-[var(--color-text-muted)]\">{email}</p>\n ) : null}\n <button\n type=\"button\"\n onClick={onPrimary}\n className=\"mt-6 inline-flex items-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90\"\n >\n <Home size={16} />\n {primaryLabel}\n </button>\n </div>\n </PageShell>\n );\n}\n\nexport function OrganizationInviteAcceptancePage() {\n useOrganizationPageView('organizations.invite-acceptance');\n const tr = useTr();\n const { inviteId = '' } = useParams<{ inviteId: string }>();\n const { basePath = '/organizations', navigate, currentUser, authToken } = useOrgConsole();\n\n const isAuthenticated = Boolean(authToken || currentUser?.email);\n const { invite, loading, error, refetch } = useOrganizationInvite(inviteId, isAuthenticated);\n const { acceptInvite } = useAcceptInvite();\n const { rejectInvite } = useRejectInvite();\n\n const [processing, setProcessing] = useState<'accept' | 'reject' | null>(null);\n const [actionError, setActionError] = useState<string | null>(null);\n\n const goToInvitesList = () => navigate(`${basePath}/invites/organizations`);\n\n const handleAccept = async () => {\n if (!inviteId) return;\n try {\n setProcessing('accept');\n setActionError(null);\n await acceptInvite(inviteId);\n navigate(invite?.organizationId ? `${basePath}/${invite.organizationId}` : basePath);\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr('members.inviteAcceptance.acceptFailed', 'Failed to accept invitation')\n );\n } finally {\n setProcessing(null);\n }\n };\n\n const handleReject = async () => {\n if (!inviteId) return;\n try {\n setProcessing('reject');\n setActionError(null);\n await rejectInvite(inviteId);\n goToInvitesList();\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr('members.inviteAcceptance.rejectFailed', 'Failed to reject invitation')\n );\n } finally {\n setProcessing(null);\n }\n };\n\n // Unauthenticated — invitees must sign in with the invited email first.\n if (!isAuthenticated) {\n const returnTo = encodeURIComponent(`${basePath}/invite/${inviteId}`);\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8\">\n <div className=\"mb-4 flex items-center gap-3\">\n <div className=\"rounded-xl bg-[var(--color-bg-subtle)] p-3\">\n <Mail size={24} className=\"text-[var(--color-text-primary)]\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.inviteAcceptance.signInRequired', 'Sign in to view your invitation')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr(\n 'members.inviteAcceptance.signInDescription',\n 'Organization invitations require an authenticated account before you can review or accept them.'\n )}\n </p>\n </div>\n </div>\n <p className=\"mb-6 text-sm text-[var(--color-text-muted)]\">\n {tr(\n 'members.inviteAcceptance.signInHint',\n 'Use the same email address that received the invitation, then come back here to accept or decline it.'\n )}\n </p>\n <div className=\"flex flex-col gap-3 sm:flex-row\">\n <a\n href={`/auth/login?returnTo=${returnTo}`}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-6 py-2.5 font-medium text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90\"\n >\n {tr('members.inviteAcceptance.signInButton', 'Sign In')}\n </a>\n <button\n type=\"button\"\n onClick={() => navigate(basePath)}\n className=\"inline-flex items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-2.5 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]\"\n >\n <Home size={16} />\n {tr('members.inviteAcceptance.backToOrganizations', 'Back to Organizations')}\n </button>\n </div>\n </div>\n </PageShell>\n );\n }\n\n if (loading) {\n return (\n <PageShell>\n <div className=\"flex min-h-[300px] items-center justify-center rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)]\">\n <Loader2 size={28} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n </PageShell>\n );\n }\n\n if (error) {\n return (\n <StatusView\n icon={AlertTriangle}\n iconClass=\"bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]\"\n title={tr('members.inviteAcceptance.errorTitle', 'Error Loading Invitation')}\n description={error.message}\n primaryLabel={tr('members.inviteAcceptance.tryAgain', 'Try Again')}\n onPrimary={() => {\n void refetch();\n }}\n />\n );\n }\n\n if (!invite) {\n return (\n <StatusView\n icon={AlertTriangle}\n iconClass=\"bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)]\"\n title={tr('members.inviteAcceptance.notFoundTitle', 'Invitation Not Found')}\n description={tr(\n 'members.inviteAcceptance.notFoundDescription',\n 'This invitation link is invalid, has been removed, or was addressed to a different account.'\n )}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n const isExpiredByDate = invite.expiresAt && new Date(invite.expiresAt) < new Date();\n const effectiveStatus =\n invite.status === InviteStatus.Pending && isExpiredByDate\n ? InviteStatus.Expired\n : invite.status;\n\n if (effectiveStatus === InviteStatus.Accepted) {\n return (\n <StatusView\n icon={CheckCircle}\n iconClass=\"bg-[var(--color-status-success-bgSubtle)] text-[var(--color-status-success-text)]\"\n title={tr('members.inviteAcceptance.alreadyAcceptedTitle', 'Invitation Already Accepted')}\n description={tr(\n 'members.inviteAcceptance.alreadyAcceptedDescription',\n 'You have already accepted this invitation.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganization', 'Go to Organization')}\n onPrimary={() => navigate(`${basePath}/${invite.organizationId}`)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Rejected) {\n return (\n <StatusView\n icon={XCircle}\n iconClass=\"bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)]\"\n title={tr('members.inviteAcceptance.rejectedTitle', 'Invitation Declined')}\n description={tr(\n 'members.inviteAcceptance.rejectedDescription',\n 'This invitation was previously declined.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Expired) {\n return (\n <StatusView\n icon={Clock}\n iconClass=\"bg-[var(--color-status-warning-bgSubtle)] text-[var(--color-status-warning-text)]\"\n title={tr('members.inviteAcceptance.expiredTitle', 'Invitation Expired')}\n description={tr(\n 'members.inviteAcceptance.expiredDescription',\n 'This invitation has expired. Please contact an organization admin for a new one.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n if (effectiveStatus === InviteStatus.Cancelled) {\n return (\n <StatusView\n icon={Ban}\n iconClass=\"bg-[var(--color-status-error-bgSubtle)] text-[var(--color-status-error-text)]\"\n title={tr('members.inviteAcceptance.cancelledTitle', 'Invitation Cancelled')}\n description={tr(\n 'members.inviteAcceptance.cancelledDescription',\n 'This invitation was cancelled by the sender and is no longer valid.'\n )}\n email={invite.email}\n primaryLabel={tr('members.inviteAcceptance.goToOrganizations', 'Go to Organizations')}\n onPrimary={() => navigate(basePath)}\n />\n );\n }\n\n // PENDING — show the acceptance UI.\n const organizationName = invite.organization?.name ?? invite.organizationId;\n const expiresLabel = formatDate(invite.expiresAt);\n const busy = processing !== null;\n\n return (\n <PageShell>\n <div className=\"rounded-2xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-8\">\n <div className=\"mb-6 flex items-center gap-3\">\n <div className=\"rounded-xl bg-[var(--color-bg-subtle)] p-3\">\n <Building2 size={24} className=\"text-[var(--color-text-primary)]\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.inviteAcceptance.pageTitle', 'Organization Invitation')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('members.inviteAcceptance.pageSubtitle', \"You've been invited to join\")}{' '}\n <span className=\"font-medium text-[var(--color-text-primary)]\">\n {organizationName}\n </span>\n </p>\n </div>\n </div>\n\n <dl className=\"space-y-4 rounded-xl bg-[var(--color-bg-subtle)] p-5\">\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.emailLabel', 'Email Address')}\n </dt>\n <dd className=\"mt-1 break-all text-[var(--color-text-primary)]\">{invite.email}</dd>\n </div>\n <div className=\"flex flex-wrap items-center gap-2\">\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.roleLabel', 'Role')}\n </dt>\n <dd>\n <Badge variant=\"secondary\">{invite.role}</Badge>\n </dd>\n </div>\n {expiresLabel ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.expiresLabel', 'Expires')}\n </dt>\n <dd className=\"mt-1 flex items-center gap-2 text-[var(--color-text-primary)]\">\n <Clock size={16} className=\"text-[var(--color-text-muted)]\" />\n {expiresLabel}\n </dd>\n </div>\n ) : null}\n {invite.message ? (\n <div>\n <dt className=\"text-sm font-medium text-[var(--color-text-muted)]\">\n {tr('members.inviteAcceptance.messageLabel', 'Message')}\n </dt>\n <dd className=\"mt-1 text-[var(--color-text-secondary)]\">{invite.message}</dd>\n </div>\n ) : null}\n </dl>\n\n {actionError ? (\n <div className=\"mt-4 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <AlertTriangle size={16} className=\"shrink-0 text-[var(--color-status-error-text)]\" />\n <p className=\"text-sm text-[var(--color-status-error-text)]\">{actionError}</p>\n </div>\n ) : null}\n\n <div className=\"mt-6 flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-6 sm:flex-row\">\n <button\n type=\"button\"\n onClick={() => void handleAccept()}\n disabled={busy}\n className=\"inline-flex flex-1 items-center justify-center gap-2 rounded-lg bg-[var(--color-status-success-bg)] px-6 py-3 font-medium text-[var(--color-text-inverse)] transition-all duration-200 hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {processing === 'accept' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <CheckCircle size={16} />\n )}\n {tr('members.inviteAcceptance.acceptButton', 'Accept Invitation')}\n </button>\n <button\n type=\"button\"\n onClick={() => void handleReject()}\n disabled={busy}\n className=\"inline-flex flex-1 items-center justify-center gap-2 rounded-lg border border-[var(--color-border-default)] bg-transparent px-6 py-3 font-medium text-[var(--color-text-primary)] transition-all duration-200 hover:border-[var(--color-status-error-border)] hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)] disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {processing === 'reject' ? (\n <Loader2 size={16} className=\"animate-spin\" />\n ) : (\n <XCircle size={16} />\n )}\n {tr('members.inviteAcceptance.declineButton', 'Decline')}\n </button>\n </div>\n </div>\n </PageShell>\n );\n}\n"],"mappings":"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"}
@@ -309,7 +309,7 @@ function h({ organization: h, tenantId: g, tenantName: _, hideTenantSelector: v
309
309
  children: [/* @__PURE__ */ f("button", {
310
310
  type: "submit",
311
311
  disabled: x,
312
- className: "\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n ",
312
+ 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 ",
313
313
  children: [x && /* @__PURE__ */ d(u, {
314
314
  size: 16,
315
315
  className: "animate-spin"
@@ -318,7 +318,7 @@ function h({ organization: h, tenantId: g, tenantName: _, hideTenantSelector: v
318
318
  type: "button",
319
319
  onClick: b,
320
320
  disabled: x,
321
- className: "\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n ",
321
+ 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 ",
322
322
  children: S("common.cancel", "Cancel")
323
323
  })]
324
324
  })
@@ -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 : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create, 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=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90 hover:scale-[1.02]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[220px]\n \"\n >\n {loading && <Loader2 size={16} className=\"animate-spin\" />}\n {organization\n ? tr('organizations.form.updateSubmit', 'Update Organization')\n : tr('organizations.create.button', 'Create Organization')}\n </button>\n\n <button\n type=\"button\"\n onClick={onCancel}\n disabled={loading}\n className=\"\n px-6 py-2.5\n bg-transparent\n text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-bg-subtle)]\n active:scale-[0.98]\n transition-all duration-200\n font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n sm:min-w-[140px]\n \"\n >\n {tr('common.cancel', 'Cancel')}\n </button>\n </div>\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"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 : createOrganizationSchema;\n schema.parse(formData);\n setErrors({});\n return true;\n } catch (error) {\n if (error instanceof ZodError) {\n const newErrors: Record<string, string> = {};\n error.issues.forEach((issue) => {\n const path = issue.path.join('.');\n newErrors[path] = issue.message;\n });\n setErrors(newErrors);\n }\n return false;\n }\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) {\n return;\n }\n\n const submitData = organization\n ? {\n // Update only changed fields\n name: formData.name !== organization.name ? formData.name : undefined,\n description:\n formData.description !== organization.description ? formData.description : undefined,\n logo: formData.logo !== organization.logo ? formData.logo : undefined,\n website: formData.website !== organization.website ? formData.website : undefined,\n }\n : {\n // Create with all fields\n tenantId: formData.tenantId,\n name: formData.name,\n slug: formData.slug,\n description: formData.description || undefined,\n logo: formData.logo || undefined,\n website: formData.website || undefined,\n };\n\n await onSubmit(submitData);\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-6\">\n {/* Tenant ID (only for create, 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=\"https://example.com/logo.png\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Website */}\n <div>\n <label\n htmlFor=\"website\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('organizations.detail.website', 'Website')}\n </label>\n <input\n id=\"website\"\n type=\"url\"\n value={formData.website}\n onChange={(e) => setFormData({ ...formData, website: e.target.value })}\n aria-label={tr('organizations.detail.website', 'Website')}\n className=\"\n w-full px-3 py-2\n bg-[var(--color-bg-surface)]\n border border-[var(--color-border-default)]\n rounded-lg\n text-[var(--color-text-primary)]\n placeholder:text-[var(--color-text-muted)]\n focus:outline-none focus:ring-2 focus:ring-[var(--color-border-focus)]\n disabled:bg-[var(--color-bg-muted)] disabled:cursor-not-allowed\n \"\n placeholder=\"https://example.com\"\n disabled={loading}\n autoComplete=\"url\"\n />\n </div>\n\n {/* Form Actions */}\n <div className=\"flex flex-col gap-3 border-t border-[var(--color-border-default)] pt-4 sm:flex-row\">\n <button\n type=\"submit\"\n disabled={loading}\n className=\"\n inline-flex items-center gap-2 px-6 py-2.5\n bg-[var(--color-action-primary-bg)]\n text-[var(--color-action-primary-text)]\n rounded-lg\n hover:opacity-90\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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"}
@@ -35,7 +35,7 @@ function p() {
35
35
  /* @__PURE__ */ f("button", {
36
36
  type: "button",
37
37
  onClick: E,
38
- className: "\n inline-flex items-center gap-2 mb-4\n text-[var(--color-text-secondary)]\n hover:text-[var(--color-text-primary)]\n active:scale-[0.98]\n transition-all duration-200\n ",
38
+ 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 ",
39
39
  children: [/* @__PURE__ */ d(l, { size: 20 }), /* @__PURE__ */ d("span", {
40
40
  className: "text-sm font-medium",
41
41
  children: p("organizations.create.back", "Back to Organizations")
@@ -1 +1 @@
1
- {"version":3,"file":"OrganizationCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n // 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-300\">\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 active:scale-[0.98]\n transition-all duration-200\n \"\n >\n <ArrowLeft size={20} />\n <span className=\"text-sm font-medium\">\n {tr('organizations.create.back', 'Back to Organizations')}\n </span>\n </button>\n\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)] mb-2\">\n {tr('organizations.create.title', 'Create Organization')}\n </h1>\n <p className=\"text-[var(--color-text-secondary)]\">\n {tr('organizations.create.subtitle', 'Add a new organization to a tenant')}\n </p>\n </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n hideTenantSelector={hideTenantSelector}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,IAAyB;AACvC,GAAwB,uBAAuB;CAC/C,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,kBAAkB,gBAAa,GAAe,EACrE,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,uBAAoB,eAAY,GAAuB,EACzD,EAAE,eAAY,EAAW,KAAK,GAAG,GAAK,EAKtC,IAAe,EAAQ,MAAM,MAAW,EAAO,SAAS,SAAS,EACjE,IAAiB,IACnB,EAAQ,MAAM,MAAW,EAAO,OAAO,EAAS,GAC/C,MAAiB,EAAQ,UAAU,IAAI,EAAQ,KAAK,KAAA,IAInD,IAAqB,EAAQ,UAAU,GAEvC,IAAe,OAAO,MAA4D;AACtF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EACgC;AACnD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,+BAA+B,gCAAgC,CACrE;AAGH,GAAI,EAAO,MAAM,uBACf,EAAM,QAAQ,EAAG,gCAAgC,oCAAoC,CAAC,EACtF,EAAS,GAAG,EAAS,GAAG,EAAO,KAAK,mBAAmB,KAAK;WAEvD,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,+BAA+B,gCAAgC,CACvE;;IAIC,UAAqB;AACzB,IAAS,EAAS;;AAwBpB,QArBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAcxC,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,6BAA6B,wBAAwB;MACpD,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,sBAAsB;KACrD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,iCAAiC,qCAAqC;KACxE,CAAA;IACA;MAGN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,KAAY,GAAgB;KACtC,YAAY,GAAgB;KACR;KACpB,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MArDJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,qCACA,qDACD;GACG,CAAA;EACF,CAAA"}
1
+ {"version":3,"file":"OrganizationCreatePage.js","names":[],"sources":["../../../../../src/organization-management/modules/organizations/pages/OrganizationCreatePage.tsx"],"sourcesContent":["import { ArrowLeft } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useCreateOrganization } from '../hooks/useOrganizations';\nimport { useTenants } from '../../workspaces/hooks/useWorkspaces';\nimport { OrganizationForm } from '../components/OrganizationForm';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\nimport { useOrganizationPageView } from '../../../hooks/useOrganizationPageView';\nimport type { CreateOrganizationInput, UpdateOrganizationInput } from '../types';\n\n/**\n * Organization Create Page\n * Form to create a new organization\n */\nexport function OrganizationCreatePage() {\n useOrganizationPageView('organizations.create');\n const tr = useTr();\n const { navigate, basePath = '/organizations', tenantId } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { createOrganization, loading } = useCreateOrganization();\n const { tenants } = useTenants(100, 0, true);\n\n // 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-300\">\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 </div>\n\n {/* Form Card */}\n <div className=\"max-w-2xl\">\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6 shadow-[var(--shadow-elevation-1)]\">\n <OrganizationForm\n tenantId={tenantId || selectedTenant?.id}\n tenantName={selectedTenant?.name}\n hideTenantSelector={hideTenantSelector}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAiBA,SAAgB,IAAyB;AACvC,GAAwB,uBAAuB;CAC/C,IAAM,IAAK,GAAO,EACZ,EAAE,aAAU,cAAW,kBAAkB,gBAAa,GAAe,EACrE,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,uBAAoB,eAAY,GAAuB,EACzD,EAAE,eAAY,EAAW,KAAK,GAAG,GAAK,EAKtC,IAAe,EAAQ,MAAM,MAAW,EAAO,SAAS,SAAS,EACjE,IAAiB,IACnB,EAAQ,MAAM,MAAW,EAAO,OAAO,EAAS,GAC/C,MAAiB,EAAQ,UAAU,IAAI,EAAQ,KAAK,KAAA,IAInD,IAAqB,EAAQ,UAAU,GAEvC,IAAe,OAAO,MAA4D;AACtF,MAAI;GAEF,IAAM,IAAS,MAAM,EADF,EACgC;AACnD,OAAI,EAAO,QAAQ,OACjB,OAAU,MACR,EAAO,OAAO,IAAI,WAChB,EAAG,+BAA+B,gCAAgC,CACrE;AAGH,GAAI,EAAO,MAAM,uBACf,EAAM,QAAQ,EAAG,gCAAgC,oCAAoC,CAAC,EACtF,EAAS,GAAG,EAAS,GAAG,EAAO,KAAK,mBAAmB,KAAK;WAEvD,GAAO;AACd,KAAM,MACJ,aAAiB,QACb,EAAM,UACN,EAAG,+BAA+B,gCAAgC,CACvE;;IAIC,UAAqB;AACzB,IAAS,EAAS;;AAwBpB,QArBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,WAAW,GAcxC,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,WAAU;eAHZ,CAWE,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA,EACvB,kBAAC,QAAD;MAAM,WAAU;gBACb,EAAG,6BAA6B,wBAAwB;MACpD,CAAA,CACA;;IAET,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,8BAA8B,sBAAsB;KACrD,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAG,iCAAiC,qCAAqC;KACxE,CAAA;IACA;MAGN,kBAAC,OAAD;GAAK,WAAU;aACb,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,UAAU,KAAY,GAAgB;KACtC,YAAY,GAAgB;KACR;KACpB,UAAU;KACV,UAAU;KACD;KACT,CAAA;IACE,CAAA;GACF,CAAA,CACF;MArDJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EACC,qCACA,qDACD;GACG,CAAA;EACF,CAAA"}
@@ -63,7 +63,7 @@ function w() {
63
63
  children: [/* @__PURE__ */ x("button", {
64
64
  type: "button",
65
65
  onClick: () => E(D),
66
- className: "inline-flex min-h-10 items-center gap-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:text-[var(--color-text-primary)] active:scale-[0.98]",
66
+ className: "inline-flex min-h-10 items-center gap-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:text-[var(--color-text-primary)]",
67
67
  children: [/* @__PURE__ */ b(p, {
68
68
  size: 20,
69
69
  "aria-hidden": "true"
@@ -125,7 +125,7 @@ function w() {
125
125
  O(s.ORG_UPDATE) && /* @__PURE__ */ x("button", {
126
126
  type: "button",
127
127
  onClick: () => E(`${D}/${T}/edit`),
128
- className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
128
+ className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90",
129
129
  children: [/* @__PURE__ */ b(g, {
130
130
  size: 16,
131
131
  "aria-hidden": "true"
@@ -264,7 +264,7 @@ function w() {
264
264
  })] }), O(s.WORKSPACE_CREATE) && /* @__PURE__ */ x("button", {
265
265
  type: "button",
266
266
  onClick: () => E(`${D}/workspaces/new?organizationId=${T}`),
267
- className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90 active:scale-[0.98]",
267
+ className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg bg-[var(--color-action-primary-bg)] px-4 py-2.5 text-[var(--color-action-primary-text)] transition-all duration-200 hover:opacity-90",
268
268
  children: [/* @__PURE__ */ b(m, {
269
269
  size: 16,
270
270
  "aria-hidden": "true"