@burdenoff/microfe-organization 2026.525.3 → 2026.527.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/organization-management/AdminOrganizationRoutes.js +1 -1
- package/dist/organization-management/AdminOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/UserOrganizationRoutes.js +1 -1
- package/dist/organization-management/UserOrganizationRoutes.js.map +1 -1
- package/dist/organization-management/modules/members/components/InviteMemberForm.js +2 -0
- package/dist/organization-management/modules/members/components/InviteMemberForm.js.map +1 -1
- package/dist/organization-management/modules/members/components/MyInviteCard.js +2 -0
- package/dist/organization-management/modules/members/components/MyInviteCard.js.map +1 -1
- package/dist/organization-management/modules/members/components/PendingInvitesList.js +2 -0
- package/dist/organization-management/modules/members/components/PendingInvitesList.js.map +1 -1
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js +1 -0
- package/dist/organization-management/modules/members/pages/InviteMemberPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js +2 -1
- package/dist/organization-management/modules/members/pages/MemberDetailPage.js.map +1 -1
- package/dist/organization-management/modules/members/pages/MembersListPage.js +10 -2
- package/dist/organization-management/modules/members/pages/MembersListPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js +6 -0
- package/dist/organization-management/modules/organizations/components/OrganizationForm.js.map +1 -1
- package/dist/organization-management/modules/organizations/hooks/useOrgsWithPermission.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js +1 -0
- package/dist/organization-management/modules/organizations/pages/OrganizationCreatePage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js +6 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationDetailPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js +1 -0
- package/dist/organization-management/modules/organizations/pages/OrganizationEditPage.js.map +1 -1
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js +15 -4
- package/dist/organization-management/modules/organizations/pages/OrganizationsListPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/AddWorkspaceMemberDialog.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js +5 -5
- package/dist/organization-management/modules/workspaces/components/WorkspaceCard.js.map +1 -1
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js +6 -2
- package/dist/organization-management/modules/workspaces/components/WorkspaceForm.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaceMembers.js.map +1 -1
- package/dist/organization-management/modules/workspaces/hooks/useWorkspaces.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js +1 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspaceCreatePage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js +8 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceDetailPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js +1 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspaceEditPage.js.map +1 -1
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js +10 -0
- package/dist/organization-management/modules/workspaces/pages/WorkspacesListPage.js.map +1 -1
- package/dist/organization-management/providers/OrgConsoleProvider.js +1 -1
- package/dist/organization-management/providers/OrgConsoleProvider.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js +25 -86
- package/dist/organization-management/shared/components/dialog/ConfirmDialog.js.map +1 -1
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js +69 -0
- package/dist/organization-management/shared/components/dialog/ConfirmDialogView.js.map +1 -0
- package/dist/organization-management/shared/components/feedback/EmptyState.js +1 -0
- package/dist/organization-management/shared/components/feedback/EmptyState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js +2 -1
- package/dist/organization-management/shared/components/feedback/ErrorState.js.map +1 -1
- package/dist/organization-management/shared/components/feedback/LoadingState.js +1 -2
- package/dist/organization-management/shared/components/feedback/LoadingState.js.map +1 -1
- package/dist/organization-management/shared/components/input/SearchInput.js +1 -0
- package/dist/organization-management/shared/components/input/SearchInput.js.map +1 -1
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js +1 -1
- package/dist/organization-management/shared/components/input/SelectionCheckbox.js.map +1 -1
- package/dist/organization-management/shared/types/graphql.js.map +1 -1
- package/package.json +2 -1
|
@@ -16,7 +16,7 @@ function S({ paramName: e }) {
|
|
|
16
16
|
function C() {
|
|
17
17
|
return /* @__PURE__ */ a("div", {
|
|
18
18
|
className: "flex items-center justify-center min-h-[200px]",
|
|
19
|
-
children: /* @__PURE__ */ a("div", { className: "
|
|
19
|
+
children: /* @__PURE__ */ a("div", { className: "size-8 animate-spin rounded-full border-2 border-[var(--color-border-default)] border-t-[var(--color-action-primary-bg)]" })
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
function w() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AdminOrganizationRoutes.js","names":[],"sources":["../../src/organization-management/AdminOrganizationRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Outlet, Route, Routes, useParams } from 'react-router-dom';\nimport { useFeatureFlagDetails } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { OrgScopedPermissions } from './providers/OrgScopedPermissions';\n\n// Eager load - main entry point page\nimport { OrganizationsListPage } from './modules/organizations/pages/OrganizationsListPage';\n\n// Lazy load - secondary pages\nconst OrganizationCreatePage = lazy(() =>\n import('./modules/organizations/pages/OrganizationCreatePage').then((m) => ({\n default: m.OrganizationCreatePage,\n }))\n);\nconst OrganizationDetailPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationDetailPage').then((m) => ({\n default: m.OrganizationDetailPage,\n }))\n);\nconst OrganizationEditPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationEditPage').then((m) => ({\n default: m.OrganizationEditPage,\n }))\n);\n\n// Workspaces - lazy load all\nconst WorkspacesListPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspacesListPage').then((m) => ({\n default: m.WorkspacesListPage,\n }))\n);\nconst WorkspaceCreatePage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceCreatePage').then((m) => ({\n default: m.WorkspaceCreatePage,\n }))\n);\nconst WorkspaceDetailPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceDetailPage').then((m) => ({\n default: m.WorkspaceDetailPage,\n }))\n);\nconst WorkspaceEditPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceEditPage').then((m) => ({\n default: m.WorkspaceEditPage,\n }))\n);\n\n// Members - lazy load all\nconst MembersListPage = lazy(() =>\n import('./modules/members/pages/MembersListPage').then((m) => ({ default: m.MembersListPage }))\n);\nconst MemberDetailPage = lazy(() =>\n import('./modules/members/pages/MemberDetailPage').then((m) => ({ default: m.MemberDetailPage }))\n);\nconst InviteMemberPage = lazy(() =>\n import('./modules/members/pages/InviteMemberPage').then((m) => ({ default: m.InviteMemberPage }))\n);\nconst MyInvitesPage = lazy(() =>\n import('./modules/members/pages/MyInvitesPage').then((m) => ({ default: m.MyInvitesPage }))\n);\n\n/**\n * Layout wrapper that scopes the PermissionProvider to the current\n * organization's id. This layout owns the `:id` or `:organizationId` URL\n * segment directly (see route declarations below) so\n * `useParams()[paramName]` is guaranteed to be populated and the\n * `OrgScopedPermissions` provider always wraps its descendants. Mirrors\n * the helper in UserOrganizationRoutes — keep them aligned.\n */\nfunction OrgScopedRouteLayout({ paramName }: { paramName: 'id' | 'organizationId' }) {\n const params = useParams<Record<string, string | undefined>>();\n const orgId = params[paramName];\n if (!orgId) return <Outlet />;\n return (\n <OrgScopedPermissions organizationId={orgId}>\n <Outlet />\n </OrgScopedPermissions>\n );\n}\n\n/**\n * Loading fallback for lazy-loaded pages\n */\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <div className=\"
|
|
1
|
+
{"version":3,"file":"AdminOrganizationRoutes.js","names":[],"sources":["../../src/organization-management/AdminOrganizationRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Outlet, Route, Routes, useParams } from 'react-router-dom';\nimport { useFeatureFlagDetails } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { OrgScopedPermissions } from './providers/OrgScopedPermissions';\n\n// Eager load - main entry point page\nimport { OrganizationsListPage } from './modules/organizations/pages/OrganizationsListPage';\n\n// Lazy load - secondary pages\nconst OrganizationCreatePage = lazy(() =>\n import('./modules/organizations/pages/OrganizationCreatePage').then((m) => ({\n default: m.OrganizationCreatePage,\n }))\n);\nconst OrganizationDetailPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationDetailPage').then((m) => ({\n default: m.OrganizationDetailPage,\n }))\n);\nconst OrganizationEditPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationEditPage').then((m) => ({\n default: m.OrganizationEditPage,\n }))\n);\n\n// Workspaces - lazy load all\nconst WorkspacesListPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspacesListPage').then((m) => ({\n default: m.WorkspacesListPage,\n }))\n);\nconst WorkspaceCreatePage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceCreatePage').then((m) => ({\n default: m.WorkspaceCreatePage,\n }))\n);\nconst WorkspaceDetailPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceDetailPage').then((m) => ({\n default: m.WorkspaceDetailPage,\n }))\n);\nconst WorkspaceEditPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceEditPage').then((m) => ({\n default: m.WorkspaceEditPage,\n }))\n);\n\n// Members - lazy load all\nconst MembersListPage = lazy(() =>\n import('./modules/members/pages/MembersListPage').then((m) => ({ default: m.MembersListPage }))\n);\nconst MemberDetailPage = lazy(() =>\n import('./modules/members/pages/MemberDetailPage').then((m) => ({ default: m.MemberDetailPage }))\n);\nconst InviteMemberPage = lazy(() =>\n import('./modules/members/pages/InviteMemberPage').then((m) => ({ default: m.InviteMemberPage }))\n);\nconst MyInvitesPage = lazy(() =>\n import('./modules/members/pages/MyInvitesPage').then((m) => ({ default: m.MyInvitesPage }))\n);\n\n/**\n * Layout wrapper that scopes the PermissionProvider to the current\n * organization's id. This layout owns the `:id` or `:organizationId` URL\n * segment directly (see route declarations below) so\n * `useParams()[paramName]` is guaranteed to be populated and the\n * `OrgScopedPermissions` provider always wraps its descendants. Mirrors\n * the helper in UserOrganizationRoutes — keep them aligned.\n */\nfunction OrgScopedRouteLayout({ paramName }: { paramName: 'id' | 'organizationId' }) {\n const params = useParams<Record<string, string | undefined>>();\n const orgId = params[paramName];\n if (!orgId) return <Outlet />;\n return (\n <OrgScopedPermissions organizationId={orgId}>\n <Outlet />\n </OrgScopedPermissions>\n );\n}\n\n/**\n * Loading fallback for lazy-loaded pages\n */\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <div className=\"size-8 animate-spin rounded-full border-2 border-[var(--color-border-default)] border-t-[var(--color-action-primary-bg)]\" />\n </div>\n );\n}\n\n/**\n * AdminOrganizationRoutes\n *\n * Full admin routing surface for the organization management MFE,\n * mounted by the admin application. Exposes all pages:\n * organizations CRUD, workspaces CRUD, members management, and\n * user-level invites.\n *\n * Routes start from root (/) since parent route is /organizations/*\n *\n * Lazy loading strategy:\n * - OrganizationsListPage: Eager loaded (main entry point)\n * - All other pages: Lazy loaded for better initial bundle size\n */\nexport function AdminOrganizationRoutes() {\n // Keep aligned with the platform feature-flag default-allow policy: an\n // unseeded flag must not disable shipped CRUDL. Explicit false still gates a\n // surface off when admins configure the flag.\n const createDetails = useFeatureFlagDetails('organization.create');\n const workspacesDetails = useFeatureFlagDetails('organization.workspaces');\n const membersDetails = useFeatureFlagDetails('organization.members');\n const inviteDetails = useFeatureFlagDetails('organization.invite');\n const createEnabled = createDetails ? Boolean(createDetails.value) : true;\n const workspacesEnabled = workspacesDetails ? Boolean(workspacesDetails.value) : true;\n const membersEnabled = membersDetails ? Boolean(membersDetails.value) : true;\n const inviteEnabled = inviteDetails ? Boolean(inviteDetails.value) : true;\n\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <Routes>\n {/* Organizations - index is the list (eager loaded). Admin app\n renders the platform-wide list (organizations query); product-app\n shells mounting this page should leave the default `mode=\"my\"`. */}\n <Route index element={<OrganizationsListPage mode=\"admin\" />} />\n {createEnabled && <Route path=\"new\" element={<OrganizationCreatePage />} />}\n\n {/* Per-org routes — the layout OWNS the `:id` segment so\n useParams().id is guaranteed to be set inside\n OrgScopedPermissions. */}\n <Route path=\":id\" element={<OrgScopedRouteLayout paramName=\"id\" />}>\n <Route index element={<OrganizationDetailPage />} />\n <Route path=\"edit\" element={<OrganizationEditPage />} />\n </Route>\n\n {/* Workspaces — literal \"workspaces\" prefix ranks above the bare\n dynamic `:id` / `:organizationId` segments. */}\n {workspacesEnabled && <Route path=\"workspaces\" element={<WorkspacesListPage />} />}\n {workspacesEnabled && <Route path=\"workspaces/new\" element={<WorkspaceCreatePage />} />}\n {workspacesEnabled && <Route path=\"workspaces/:id\" element={<WorkspaceDetailPage />} />}\n {workspacesEnabled && <Route path=\"workspaces/:id/edit\" element={<WorkspaceEditPage />} />}\n\n {/* My Invites - user-level, not org-scoped. Declared before the\n `:organizationId` block so the literal \"invites\" wins the rank\n match. */}\n <Route path=\"invites\" element={<MyInvitesPage />} />\n\n {/* Member routes — layout OWNS the `:organizationId` segment for\n the same reason as the detail block above. */}\n <Route path=\":organizationId\" element={<OrgScopedRouteLayout paramName=\"organizationId\" />}>\n {inviteEnabled && <Route path=\"members/invite\" element={<InviteMemberPage />} />}\n {membersEnabled && <Route path=\"members\" element={<MembersListPage />} />}\n {membersEnabled && <Route path=\"members/:userId\" element={<MemberDetailPage />} />}\n </Route>\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ function x({ paramName: e }) {
|
|
|
15
15
|
function S() {
|
|
16
16
|
return /* @__PURE__ */ i("div", {
|
|
17
17
|
className: "flex items-center justify-center min-h-[200px]",
|
|
18
|
-
children: /* @__PURE__ */ i("div", { className: "
|
|
18
|
+
children: /* @__PURE__ */ i("div", { className: "size-8 animate-spin rounded-full border-2 border-[var(--color-border-default)] border-t-[var(--color-action-primary-bg)]" })
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
21
|
function C() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserOrganizationRoutes.js","names":[],"sources":["../../src/organization-management/UserOrganizationRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Outlet, Route, Routes, useParams } from 'react-router-dom';\n\n// Eager load — main entry point page\nimport { OrganizationsListPage } from './modules/organizations/pages/OrganizationsListPage';\nimport { OrgScopedPermissions } from './providers/OrgScopedPermissions';\n\n// Lazy load — secondary pages. Same set as the admin surface but the list\n// page below renders in `mode=\"my\"` (membership-scoped), and per-action\n// permission gates inside each page (hasPermission(PERMISSIONS.ORG_*))\n// already hide buttons the user can't use.\nconst OrganizationCreatePage = lazy(() =>\n import('./modules/organizations/pages/OrganizationCreatePage').then((m) => ({\n default: m.OrganizationCreatePage,\n }))\n);\nconst OrganizationDetailPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationDetailPage').then((m) => ({\n default: m.OrganizationDetailPage,\n }))\n);\nconst OrganizationEditPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationEditPage').then((m) => ({\n default: m.OrganizationEditPage,\n }))\n);\nconst WorkspacesListPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspacesListPage').then((m) => ({\n default: m.WorkspacesListPage,\n }))\n);\nconst WorkspaceCreatePage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceCreatePage').then((m) => ({\n default: m.WorkspaceCreatePage,\n }))\n);\nconst WorkspaceDetailPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceDetailPage').then((m) => ({\n default: m.WorkspaceDetailPage,\n }))\n);\nconst WorkspaceEditPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceEditPage').then((m) => ({\n default: m.WorkspaceEditPage,\n }))\n);\nconst MembersListPage = lazy(() =>\n import('./modules/members/pages/MembersListPage').then((m) => ({ default: m.MembersListPage }))\n);\nconst MemberDetailPage = lazy(() =>\n import('./modules/members/pages/MemberDetailPage').then((m) => ({ default: m.MemberDetailPage }))\n);\nconst InviteMemberPage = lazy(() =>\n import('./modules/members/pages/InviteMemberPage').then((m) => ({ default: m.InviteMemberPage }))\n);\nconst MyInvitesPage = lazy(() =>\n import('./modules/members/pages/MyInvitesPage').then((m) => ({ default: m.MyInvitesPage }))\n);\n\n/**\n * Layout wrapper that scopes the PermissionProvider to the current\n * organization's id (read from the URL). This layout owns the `:id` or\n * `:organizationId` URL segment directly (see route declarations below),\n * so `useParams()[paramName]` is guaranteed to be populated and the\n * `OrgScopedPermissions` provider always wraps its descendants. The\n * previous shape mounted this on a pathless parent and relied on child\n * routes to declare the param, which could silently fall through to\n * tenant-scoped permissions if a future refactor moved the layout above\n * a tree that doesn't own the segment.\n */\nfunction OrgScopedRouteLayout({ paramName }: { paramName: 'id' | 'organizationId' }) {\n const params = useParams<Record<string, string | undefined>>();\n const orgId = params[paramName];\n if (!orgId) return <Outlet />;\n return (\n <OrgScopedPermissions organizationId={orgId}>\n <Outlet />\n </OrgScopedPermissions>\n );\n}\n\n/**\n * Loading fallback for lazy-loaded pages\n */\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <div className=\"h-8 w-8 animate-spin rounded-full border-2 border-[var(--color-border-default)] border-t-[var(--color-action-primary-bg)]\" />\n </div>\n );\n}\n\n/**\n * UserOrganizationRoutes\n *\n * User-facing routing surface for the organization MFE, mounted by product\n * application shells (vibecontrols, etc.). Exposes the same shape as the\n * admin routing surface but the list page renders membership-scoped via\n * `mode=\"my\"` (calls `myOrganizations` with `@rbac(scopeType: \"self\")`).\n *\n * Per-action authorisation lives inside each page (`hasPermission` checks\n * for create / update / delete), so users with elevated permissions on an\n * org still see those controls; users without them only see view actions.\n *\n * All routes are unconditionally mounted. Feature flags\n * (`organization.create`, `organization.workspaces`, `organization.members`,\n * `organization.invite`) are applied at the call site — buttons, nav\n * items, section headers inside the pages — not on the route graph. This\n * avoids a class of route/nav mismatches where a page unconditionally\n * navigates to a URL that has been gated off (the previous flag-gated\n * routes produced dead URLs from `OrganizationDetailPage`'s workspace\n * cards, \"Create Workspace\" CTA, \"Manage Members\" button, etc.).\n *\n * Access control:\n * - Per-action `hasPermission` checks inside each page hide controls the\n * user can't use.\n * - Backend resolvers (`organization`, `organizationMembers`, etc.)\n * enforce membership server-side via `userHasAccessToOrganization`, so\n * deep-linking to a URL the user has no access to 403s at the resolver.\n *\n * Lazy loading strategy:\n * - OrganizationsListPage: Eager loaded (main entry point)\n * - All other pages: Lazy loaded for better initial bundle size\n */\nexport function UserOrganizationRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <Routes>\n {/* Organizations — index is the membership-scoped list (eager loaded) */}\n <Route index element={<OrganizationsListPage mode=\"my\" />} />\n <Route path=\"new\" element={<OrganizationCreatePage />} />\n\n {/* Per-org routes — the layout OWNS the `:id` segment so\n `useParams().id` is guaranteed to be set inside\n OrgScopedPermissions and per-page hasPermission() resolves at\n the org's scope, not the outer tenant scope. */}\n <Route path=\":id\" element={<OrgScopedRouteLayout paramName=\"id\" />}>\n <Route index element={<OrganizationDetailPage />} />\n <Route path=\"edit\" element={<OrganizationEditPage />} />\n </Route>\n\n {/* My Invites — user-level, not org-scoped. Declared before the\n `:organizationId` block so the literal \"invites\" wins the rank\n match against the dynamic segment. */}\n <Route path=\"invites\" element={<MyInvitesPage />} />\n\n {/* Workspaces — `OrganizationDetailPage` navigates here on workspace\n card click and the \"Create Workspace\" CTA. Declared with literal\n \"workspaces\" prefix so it ranks above the bare `:organizationId`\n dynamic-segment routes below. */}\n <Route path=\"workspaces\" element={<WorkspacesListPage />} />\n <Route path=\"workspaces/new\" element={<WorkspaceCreatePage />} />\n <Route path=\"workspaces/:id\" element={<WorkspaceDetailPage />} />\n <Route path=\"workspaces/:id/edit\" element={<WorkspaceEditPage />} />\n\n {/* Members — layout OWNS the `:organizationId` segment for the\n same reason as the detail block above. The inner pages already\n read `useParams().organizationId`, so we keep the segment name\n distinct from `:id` even though both resolve to an org id. */}\n <Route path=\":organizationId\" element={<OrgScopedRouteLayout paramName=\"organizationId\" />}>\n <Route path=\"members/invite\" element={<InviteMemberPage />} />\n <Route path=\"members\" element={<MembersListPage />} />\n <Route path=\"members/:userId\" element={<MemberDetailPage />} />\n </Route>\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UserOrganizationRoutes.js","names":[],"sources":["../../src/organization-management/UserOrganizationRoutes.tsx"],"sourcesContent":["import { lazy, Suspense } from 'react';\nimport { Outlet, Route, Routes, useParams } from 'react-router-dom';\n\n// Eager load — main entry point page\nimport { OrganizationsListPage } from './modules/organizations/pages/OrganizationsListPage';\nimport { OrgScopedPermissions } from './providers/OrgScopedPermissions';\n\n// Lazy load — secondary pages. Same set as the admin surface but the list\n// page below renders in `mode=\"my\"` (membership-scoped), and per-action\n// permission gates inside each page (hasPermission(PERMISSIONS.ORG_*))\n// already hide buttons the user can't use.\nconst OrganizationCreatePage = lazy(() =>\n import('./modules/organizations/pages/OrganizationCreatePage').then((m) => ({\n default: m.OrganizationCreatePage,\n }))\n);\nconst OrganizationDetailPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationDetailPage').then((m) => ({\n default: m.OrganizationDetailPage,\n }))\n);\nconst OrganizationEditPage = lazy(() =>\n import('./modules/organizations/pages/OrganizationEditPage').then((m) => ({\n default: m.OrganizationEditPage,\n }))\n);\nconst WorkspacesListPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspacesListPage').then((m) => ({\n default: m.WorkspacesListPage,\n }))\n);\nconst WorkspaceCreatePage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceCreatePage').then((m) => ({\n default: m.WorkspaceCreatePage,\n }))\n);\nconst WorkspaceDetailPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceDetailPage').then((m) => ({\n default: m.WorkspaceDetailPage,\n }))\n);\nconst WorkspaceEditPage = lazy(() =>\n import('./modules/workspaces/pages/WorkspaceEditPage').then((m) => ({\n default: m.WorkspaceEditPage,\n }))\n);\nconst MembersListPage = lazy(() =>\n import('./modules/members/pages/MembersListPage').then((m) => ({ default: m.MembersListPage }))\n);\nconst MemberDetailPage = lazy(() =>\n import('./modules/members/pages/MemberDetailPage').then((m) => ({ default: m.MemberDetailPage }))\n);\nconst InviteMemberPage = lazy(() =>\n import('./modules/members/pages/InviteMemberPage').then((m) => ({ default: m.InviteMemberPage }))\n);\nconst MyInvitesPage = lazy(() =>\n import('./modules/members/pages/MyInvitesPage').then((m) => ({ default: m.MyInvitesPage }))\n);\n\n/**\n * Layout wrapper that scopes the PermissionProvider to the current\n * organization's id (read from the URL). This layout owns the `:id` or\n * `:organizationId` URL segment directly (see route declarations below),\n * so `useParams()[paramName]` is guaranteed to be populated and the\n * `OrgScopedPermissions` provider always wraps its descendants. The\n * previous shape mounted this on a pathless parent and relied on child\n * routes to declare the param, which could silently fall through to\n * tenant-scoped permissions if a future refactor moved the layout above\n * a tree that doesn't own the segment.\n */\nfunction OrgScopedRouteLayout({ paramName }: { paramName: 'id' | 'organizationId' }) {\n const params = useParams<Record<string, string | undefined>>();\n const orgId = params[paramName];\n if (!orgId) return <Outlet />;\n return (\n <OrgScopedPermissions organizationId={orgId}>\n <Outlet />\n </OrgScopedPermissions>\n );\n}\n\n/**\n * Loading fallback for lazy-loaded pages\n */\nfunction PageLoadingFallback() {\n return (\n <div className=\"flex items-center justify-center min-h-[200px]\">\n <div className=\"size-8 animate-spin rounded-full border-2 border-[var(--color-border-default)] border-t-[var(--color-action-primary-bg)]\" />\n </div>\n );\n}\n\n/**\n * UserOrganizationRoutes\n *\n * User-facing routing surface for the organization MFE, mounted by product\n * application shells (vibecontrols, etc.). Exposes the same shape as the\n * admin routing surface but the list page renders membership-scoped via\n * `mode=\"my\"` (calls `myOrganizations` with `@rbac(scopeType: \"self\")`).\n *\n * Per-action authorisation lives inside each page (`hasPermission` checks\n * for create / update / delete), so users with elevated permissions on an\n * org still see those controls; users without them only see view actions.\n *\n * All routes are unconditionally mounted. Feature flags\n * (`organization.create`, `organization.workspaces`, `organization.members`,\n * `organization.invite`) are applied at the call site — buttons, nav\n * items, section headers inside the pages — not on the route graph. This\n * avoids a class of route/nav mismatches where a page unconditionally\n * navigates to a URL that has been gated off (the previous flag-gated\n * routes produced dead URLs from `OrganizationDetailPage`'s workspace\n * cards, \"Create Workspace\" CTA, \"Manage Members\" button, etc.).\n *\n * Access control:\n * - Per-action `hasPermission` checks inside each page hide controls the\n * user can't use.\n * - Backend resolvers (`organization`, `organizationMembers`, etc.)\n * enforce membership server-side via `userHasAccessToOrganization`, so\n * deep-linking to a URL the user has no access to 403s at the resolver.\n *\n * Lazy loading strategy:\n * - OrganizationsListPage: Eager loaded (main entry point)\n * - All other pages: Lazy loaded for better initial bundle size\n */\nexport function UserOrganizationRoutes() {\n return (\n <Suspense fallback={<PageLoadingFallback />}>\n <Routes>\n {/* Organizations — index is the membership-scoped list (eager loaded) */}\n <Route index element={<OrganizationsListPage mode=\"my\" />} />\n <Route path=\"new\" element={<OrganizationCreatePage />} />\n\n {/* Per-org routes — the layout OWNS the `:id` segment so\n `useParams().id` is guaranteed to be set inside\n OrgScopedPermissions and per-page hasPermission() resolves at\n the org's scope, not the outer tenant scope. */}\n <Route path=\":id\" element={<OrgScopedRouteLayout paramName=\"id\" />}>\n <Route index element={<OrganizationDetailPage />} />\n <Route path=\"edit\" element={<OrganizationEditPage />} />\n </Route>\n\n {/* My Invites — user-level, not org-scoped. Declared before the\n `:organizationId` block so the literal \"invites\" wins the rank\n match against the dynamic segment. */}\n <Route path=\"invites\" element={<MyInvitesPage />} />\n\n {/* Workspaces — `OrganizationDetailPage` navigates here on workspace\n card click and the \"Create Workspace\" CTA. Declared with literal\n \"workspaces\" prefix so it ranks above the bare `:organizationId`\n dynamic-segment routes below. */}\n <Route path=\"workspaces\" element={<WorkspacesListPage />} />\n <Route path=\"workspaces/new\" element={<WorkspaceCreatePage />} />\n <Route path=\"workspaces/:id\" element={<WorkspaceDetailPage />} />\n <Route path=\"workspaces/:id/edit\" element={<WorkspaceEditPage />} />\n\n {/* Members — layout OWNS the `:organizationId` segment for the\n same reason as the detail block above. The inner pages already\n read `useParams().organizationId`, so we keep the segment name\n distinct from `:id` even though both resolve to an org id. */}\n <Route path=\":organizationId\" element={<OrgScopedRouteLayout paramName=\"organizationId\" />}>\n <Route path=\"members/invite\" element={<InviteMemberPage />} />\n <Route path=\"members\" element={<MembersListPage />} />\n <Route path=\"members/:userId\" element={<MemberDetailPage />} />\n </Route>\n </Routes>\n </Suspense>\n );\n}\n"],"mappings":"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"}
|
|
@@ -53,6 +53,7 @@ function s({ organizationId: s, onSubmit: c, onCancel: l, loading: u }) {
|
|
|
53
53
|
...f,
|
|
54
54
|
email: e.target.value
|
|
55
55
|
}),
|
|
56
|
+
"aria-label": d("members.invite.emailLabel", "Email Address"),
|
|
56
57
|
className: "\n w-full pl-10 pr-4 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 ",
|
|
57
58
|
placeholder: d("members.invite.emailPlaceholder", "user@example.com"),
|
|
58
59
|
disabled: u,
|
|
@@ -86,6 +87,7 @@ function s({ organizationId: s, onSubmit: c, onCancel: l, loading: u }) {
|
|
|
86
87
|
...f,
|
|
87
88
|
message: e.target.value
|
|
88
89
|
}),
|
|
90
|
+
"aria-label": d("members.invite.messageLabel", "Personal Message"),
|
|
89
91
|
className: "\n w-full px-4 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 resize-none\n ",
|
|
90
92
|
placeholder: d("members.invite.messagePlaceholder", "Add a personal message to the invite..."),
|
|
91
93
|
rows: 3,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteMemberForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/InviteMemberForm.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Mail, Send } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface InviteMemberFormProps {\n organizationId: string;\n onSubmit: (data: { organizationId: string; email: string; message?: string }) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\n/**\n * Invite Member Form\n * Email-based form for inviting users to an organization.\n * Role defaults to MEMBER on the backend — admin can change it after the user joins.\n */\nexport function InviteMemberForm({\n organizationId,\n onSubmit,\n onCancel,\n loading,\n}: InviteMemberFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n email: '',\n message: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await onSubmit({\n organizationId,\n email: formData.email,\n message: formData.message || undefined,\n });\n setFormData({ email: '', message: '' });\n } catch {\n // Error is surfaced by the parent page via the mutation hook's error state\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Email */}\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.emailLabel', 'Email Address')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <Mail size={16} className=\"text-[var(--color-text-muted)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n className=\"\n w-full pl-10 pr-4 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('members.invite.emailPlaceholder', 'user@example.com')}\n disabled={loading}\n required\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-muted)] mt-1\">\n {tr(\n 'members.invite.emailHelp',\n 'The user will be invited as a Member. You can change their role after they join.'\n )}\n </p>\n </div>\n\n {/* Message (optional) */}\n <div>\n <label\n htmlFor=\"message\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.messageLabel', 'Personal Message')}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({tr('common.optional', 'optional')})\n </span>\n </label>\n <textarea\n id=\"message\"\n value={formData.message}\n onChange={(e) => setFormData({ ...formData, message: e.target.value })}\n className=\"\n w-full px-4 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 resize-none\n \"\n placeholder={tr(\n 'members.invite.messagePlaceholder',\n 'Add a personal message to the invite...'\n )}\n rows={3}\n maxLength={500}\n disabled={loading}\n />\n </div>\n\n {/* 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-[180px]\n \"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : <Send size={16} />}\n {tr('members.invite.send', 'Send Invite')}\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":";;;;;AAgBA,SAAgB,EAAiB,EAC/B,mBACA,aACA,aACA,cACwB;CACxB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAS;EACvC,OAAO;EACP,SAAS;EACV,CAAC;AAgBF,QACE,kBAAC,QAAD;EAAM,UAfa,OAAO,MAAuB;AACjD,KAAE,gBAAgB;AAClB,OAAI;AAMF,IALA,MAAM,EAAS;KACb;KACA,OAAO,EAAS;KAChB,SAAS,EAAS,WAAW,KAAA;KAC9B,CAAC,EACF,EAAY;KAAE,OAAO;KAAI,SAAS;KAAI,CAAC;WACjC;;EAMsB,WAAU;YAAxC;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eAFZ;MAIG,EAAG,6BAA6B,gBAAgB;MAAE;MACnD,kBAAC,QAAD;OAAM,WAAU;iBAAwC;OAAQ,CAAA;MAC1D;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAM,MAAM;OAAI,WAAU;OAAmC,CAAA;MACzD,CAAA,EACN,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO,EAAS;MAChB,WAAW,MAAM,EAAY;OAAE,GAAG;OAAU,OAAO,EAAE,OAAO;OAAO,CAAC;MACpE,WAAU;MAUV,aAAa,EAAG,mCAAmC,mBAAmB;MACtE,UAAU;MACV,UAAA;MACA,CAAA,CACE;;IACN,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,4BACA,mFACD;KACC,CAAA;IACA,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;cAFZ;KAIG,EAAG,+BAA+B,mBAAmB;KAAE;KACxD,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAAiD;OAC7C,EAAG,mBAAmB,WAAW;OAAC;OAC/B;;KACD;OACR,kBAAC,YAAD;IACE,IAAG;IACH,OAAO,EAAS;IAChB,WAAW,MAAM,EAAY;KAAE,GAAG;KAAU,SAAS,EAAE,OAAO;KAAO,CAAC;IACtE,WAAU;IAWV,aAAa,EACX,qCACA,0CACD;IACD,MAAM;IACN,WAAW;IACX,UAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,WAAU;eAHZ,CAgBG,IAAU,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAC7E,EAAG,uBAAuB,cAAc,CAClC;QAET,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAcT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CACL;;GACD"}
|
|
1
|
+
{"version":3,"file":"InviteMemberForm.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/InviteMemberForm.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { Loader2, Mail, Send } from 'lucide-react';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface InviteMemberFormProps {\n organizationId: string;\n onSubmit: (data: { organizationId: string; email: string; message?: string }) => Promise<void>;\n onCancel: () => void;\n loading?: boolean;\n}\n\n/**\n * Invite Member Form\n * Email-based form for inviting users to an organization.\n * Role defaults to MEMBER on the backend — admin can change it after the user joins.\n */\nexport function InviteMemberForm({\n organizationId,\n onSubmit,\n onCancel,\n loading,\n}: InviteMemberFormProps) {\n const tr = useTr();\n const [formData, setFormData] = useState({\n email: '',\n message: '',\n });\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n try {\n await onSubmit({\n organizationId,\n email: formData.email,\n message: formData.message || undefined,\n });\n setFormData({ email: '', message: '' });\n } catch {\n // Error is surfaced by the parent page via the mutation hook's error state\n }\n };\n\n return (\n <form onSubmit={handleSubmit} className=\"space-y-5\">\n {/* Email */}\n <div>\n <label\n htmlFor=\"email\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.emailLabel', 'Email Address')}{' '}\n <span className=\"text-[var(--color-status-error-text)]\">*</span>\n </label>\n <div className=\"relative\">\n <div className=\"absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none\">\n <Mail size={16} className=\"text-[var(--color-text-muted)]\" />\n </div>\n <input\n id=\"email\"\n type=\"email\"\n value={formData.email}\n onChange={(e) => setFormData({ ...formData, email: e.target.value })}\n aria-label={tr('members.invite.emailLabel', 'Email Address')}\n className=\"\n w-full pl-10 pr-4 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('members.invite.emailPlaceholder', 'user@example.com')}\n disabled={loading}\n required\n />\n </div>\n <p className=\"text-xs text-[var(--color-text-muted)] mt-1\">\n {tr(\n 'members.invite.emailHelp',\n 'The user will be invited as a Member. You can change their role after they join.'\n )}\n </p>\n </div>\n\n {/* Message (optional) */}\n <div>\n <label\n htmlFor=\"message\"\n className=\"block text-sm font-medium text-[var(--color-text-primary)] mb-1\"\n >\n {tr('members.invite.messageLabel', 'Personal Message')}{' '}\n <span className=\"text-[var(--color-text-muted)]\">\n ({tr('common.optional', 'optional')})\n </span>\n </label>\n <textarea\n id=\"message\"\n value={formData.message}\n onChange={(e) => setFormData({ ...formData, message: e.target.value })}\n aria-label={tr('members.invite.messageLabel', 'Personal Message')}\n className=\"\n w-full px-4 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 resize-none\n \"\n placeholder={tr(\n 'members.invite.messagePlaceholder',\n 'Add a personal message to the invite...'\n )}\n rows={3}\n maxLength={500}\n disabled={loading}\n />\n </div>\n\n {/* 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-[180px]\n \"\n >\n {loading ? <Loader2 size={16} className=\"animate-spin\" /> : <Send size={16} />}\n {tr('members.invite.send', 'Send Invite')}\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":";;;;;AAgBA,SAAgB,EAAiB,EAC/B,mBACA,aACA,aACA,cACwB;CACxB,IAAM,IAAK,GAAO,EACZ,CAAC,GAAU,KAAe,EAAS;EACvC,OAAO;EACP,SAAS;EACV,CAAC;AAgBF,QACE,kBAAC,QAAD;EAAM,UAfa,OAAO,MAAuB;AACjD,KAAE,gBAAgB;AAClB,OAAI;AAMF,IALA,MAAM,EAAS;KACb;KACA,OAAO,EAAS;KAChB,SAAS,EAAS,WAAW,KAAA;KAC9B,CAAC,EACF,EAAY;KAAE,OAAO;KAAI,SAAS;KAAI,CAAC;WACjC;;EAMsB,WAAU;YAAxC;GAEE,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,SAAD;KACE,SAAQ;KACR,WAAU;eAFZ;MAIG,EAAG,6BAA6B,gBAAgB;MAAE;MACnD,kBAAC,QAAD;OAAM,WAAU;iBAAwC;OAAQ,CAAA;MAC1D;;IACR,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBACb,kBAAC,GAAD;OAAM,MAAM;OAAI,WAAU;OAAmC,CAAA;MACzD,CAAA,EACN,kBAAC,SAAD;MACE,IAAG;MACH,MAAK;MACL,OAAO,EAAS;MAChB,WAAW,MAAM,EAAY;OAAE,GAAG;OAAU,OAAO,EAAE,OAAO;OAAO,CAAC;MACpE,cAAY,EAAG,6BAA6B,gBAAgB;MAC5D,WAAU;MAUV,aAAa,EAAG,mCAAmC,mBAAmB;MACtE,UAAU;MACV,UAAA;MACA,CAAA,CACE;;IACN,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,4BACA,mFACD;KACC,CAAA;IACA,EAAA,CAAA;GAGN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,SAAD;IACE,SAAQ;IACR,WAAU;cAFZ;KAIG,EAAG,+BAA+B,mBAAmB;KAAE;KACxD,kBAAC,QAAD;MAAM,WAAU;gBAAhB;OAAiD;OAC7C,EAAG,mBAAmB,WAAW;OAAC;OAC/B;;KACD;OACR,kBAAC,YAAD;IACE,IAAG;IACH,OAAO,EAAS;IAChB,WAAW,MAAM,EAAY;KAAE,GAAG;KAAU,SAAS,EAAE,OAAO;KAAO,CAAC;IACtE,cAAY,EAAG,+BAA+B,mBAAmB;IACjE,WAAU;IAWV,aAAa,EACX,qCACA,0CACD;IACD,MAAM;IACN,WAAW;IACX,UAAU;IACV,CAAA,CACE,EAAA,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KACE,MAAK;KACL,UAAU;KACV,WAAU;eAHZ,CAgBG,IAAU,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EAC7E,EAAG,uBAAuB,cAAc,CAClC;QAET,kBAAC,UAAD;KACE,MAAK;KACL,SAAS;KACT,UAAU;KACV,WAAU;eAcT,EAAG,iBAAiB,SAAS;KACvB,CAAA,CACL;;GACD"}
|
|
@@ -44,6 +44,7 @@ function l({ invite: l, onAccept: u, onReject: d, accepting: f, rejecting: p })
|
|
|
44
44
|
}), /* @__PURE__ */ c("div", {
|
|
45
45
|
className: "flex flex-wrap items-center gap-2 shrink-0",
|
|
46
46
|
children: [/* @__PURE__ */ c("button", {
|
|
47
|
+
type: "button",
|
|
47
48
|
onClick: () => u(l.id),
|
|
48
49
|
disabled: f || p,
|
|
49
50
|
className: "\n inline-flex items-center gap-1.5 px-4 py-2\n bg-[var(--color-status-success-bg)] text-[var(--color-text-inverse)] rounded-lg\n hover:opacity-90\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n ",
|
|
@@ -52,6 +53,7 @@ function l({ invite: l, onAccept: u, onReject: d, accepting: f, rejecting: p })
|
|
|
52
53
|
className: "animate-spin"
|
|
53
54
|
}) : /* @__PURE__ */ s(r, { size: 14 }), m("members.myInvites.accept", "Accept")]
|
|
54
55
|
}), /* @__PURE__ */ c("button", {
|
|
56
|
+
type: "button",
|
|
55
57
|
onClick: () => d(l.id),
|
|
56
58
|
disabled: f || p,
|
|
57
59
|
className: "\n inline-flex items-center gap-1.5 px-4 py-2\n bg-transparent text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-status-error-bgSubtle)] hover:border-[var(--color-status-error-border)] hover:text-[var(--color-status-error-text)]\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n ",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MyInviteCard.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/MyInviteCard.tsx"],"sourcesContent":["import { Check, X, Building2, Clock, Loader2 } from 'lucide-react';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface MyInviteCardProps {\n invite: {\n id: string;\n email: string;\n role: string;\n expiresAt: string;\n message?: string | null;\n createdAt: string;\n organization?: {\n id: string;\n name: string;\n slug: string;\n } | null;\n };\n onAccept: (inviteId: string) => Promise<void>;\n onReject: (inviteId: string) => Promise<void>;\n accepting?: boolean;\n rejecting?: boolean;\n}\n\nexport function MyInviteCard({\n invite,\n onAccept,\n onReject,\n accepting,\n rejecting,\n}: MyInviteCardProps) {\n const tr = useTr();\n const daysUntilExpiry = Math.max(\n 0,\n Math.ceil((new Date(invite.expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5 transition-colors hover:border-[var(--color-border-focus)]\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n {/* Left: Org info */}\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"p-2 rounded-lg bg-[var(--color-bg-subtle)]\">\n <Building2 size={20} className=\"text-[var(--color-text-secondary)]\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] truncate\">\n {invite.organization?.name ?? tr('common.organization', 'Organization')}\n </h3>\n <div className=\"flex items-center gap-2 mt-1\">\n <MemberRoleBadge role={invite.role} />\n <span className=\"text-xs text-[var(--color-text-muted)] flex items-center gap-1\">\n <Clock size={12} />\n {daysUntilExpiry > 0\n ? `${tr('members.myInvites.expiresIn', 'Expires in')} ${daysUntilExpiry}d`\n : tr('members.myInvites.expiringSoon', 'Expiring soon')}\n </span>\n </div>\n {invite.message && (\n <p className=\"mt-2 text-sm text-[var(--color-text-secondary)] italic\">\n “{invite.message}”\n </p>\n )}\n </div>\n </div>\n\n {/* Right: Actions */}\n <div className=\"flex flex-wrap items-center gap-2 shrink-0\">\n <button\n onClick={() => onAccept(invite.id)}\n disabled={accepting || rejecting}\n className=\"\n inline-flex items-center gap-1.5 px-4 py-2\n bg-[var(--color-status-success-bg)] text-[var(--color-text-inverse)] rounded-lg\n hover:opacity-90\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n \"\n >\n {accepting ? <Loader2 size={14} className=\"animate-spin\" /> : <Check size={14} />}\n {tr('members.myInvites.accept', 'Accept')}\n </button>\n <button\n onClick={() => onReject(invite.id)}\n disabled={accepting || rejecting}\n className=\"\n inline-flex items-center gap-1.5 px-4 py-2\n bg-transparent text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-status-error-bgSubtle)] hover:border-[var(--color-status-error-border)] hover:text-[var(--color-status-error-text)]\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n \"\n >\n {rejecting ? <Loader2 size={14} className=\"animate-spin\" /> : <X size={14} />}\n {tr('members.myInvites.decline', 'Decline')}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAwBA,SAAgB,EAAa,EAC3B,WACA,aACA,aACA,cACA,gBACoB;CACpB,IAAM,IAAK,GAAO,EACZ,IAAkB,KAAK,IAC3B,GACA,KAAK,MAAM,IAAI,KAAK,EAAO,UAAU,CAAC,SAAS,GAAG,KAAK,KAAK,KAAK,MAAO,KAAK,KAAK,IAAI,CACvF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAW,MAAM;MAAI,WAAU;MAAuC,CAAA;KAClE,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,cAAc,QAAQ,EAAG,uBAAuB,eAAe;OACpE,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAiB,MAAM,EAAO,MAAQ,CAAA,EACtC,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,EAClB,IAAkB,IACf,GAAG,EAAG,+BAA+B,aAAa,CAAC,GAAG,EAAgB,KACtE,EAAG,kCAAkC,gBAAgB,CACpD;UACH;;MACL,EAAO,WACN,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAsE;QAC5D,EAAO;QAAQ;QACrB;;MAEF;OACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;
|
|
1
|
+
{"version":3,"file":"MyInviteCard.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/MyInviteCard.tsx"],"sourcesContent":["import { Check, X, Building2, Clock, Loader2 } from 'lucide-react';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface MyInviteCardProps {\n invite: {\n id: string;\n email: string;\n role: string;\n expiresAt: string;\n message?: string | null;\n createdAt: string;\n organization?: {\n id: string;\n name: string;\n slug: string;\n } | null;\n };\n onAccept: (inviteId: string) => Promise<void>;\n onReject: (inviteId: string) => Promise<void>;\n accepting?: boolean;\n rejecting?: boolean;\n}\n\nexport function MyInviteCard({\n invite,\n onAccept,\n onReject,\n accepting,\n rejecting,\n}: MyInviteCardProps) {\n const tr = useTr();\n const daysUntilExpiry = Math.max(\n 0,\n Math.ceil((new Date(invite.expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24))\n );\n\n return (\n <div className=\"rounded-xl border border-[var(--color-border-default)] bg-[var(--color-bg-surface)] p-5 transition-colors hover:border-[var(--color-border-focus)]\">\n <div className=\"flex flex-col gap-4 lg:flex-row lg:items-start lg:justify-between\">\n {/* Left: Org info */}\n <div className=\"flex items-start gap-3 min-w-0\">\n <div className=\"p-2 rounded-lg bg-[var(--color-bg-subtle)]\">\n <Building2 size={20} className=\"text-[var(--color-text-secondary)]\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"font-semibold text-[var(--color-text-primary)] truncate\">\n {invite.organization?.name ?? tr('common.organization', 'Organization')}\n </h3>\n <div className=\"flex items-center gap-2 mt-1\">\n <MemberRoleBadge role={invite.role} />\n <span className=\"text-xs text-[var(--color-text-muted)] flex items-center gap-1\">\n <Clock size={12} />\n {daysUntilExpiry > 0\n ? `${tr('members.myInvites.expiresIn', 'Expires in')} ${daysUntilExpiry}d`\n : tr('members.myInvites.expiringSoon', 'Expiring soon')}\n </span>\n </div>\n {invite.message && (\n <p className=\"mt-2 text-sm text-[var(--color-text-secondary)] italic\">\n “{invite.message}”\n </p>\n )}\n </div>\n </div>\n\n {/* Right: Actions */}\n <div className=\"flex flex-wrap items-center gap-2 shrink-0\">\n <button type=\"button\"\n onClick={() => onAccept(invite.id)}\n disabled={accepting || rejecting}\n className=\"\n inline-flex items-center gap-1.5 px-4 py-2\n bg-[var(--color-status-success-bg)] text-[var(--color-text-inverse)] rounded-lg\n hover:opacity-90\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n \"\n >\n {accepting ? <Loader2 size={14} className=\"animate-spin\" /> : <Check size={14} />}\n {tr('members.myInvites.accept', 'Accept')}\n </button>\n <button type=\"button\"\n onClick={() => onReject(invite.id)}\n disabled={accepting || rejecting}\n className=\"\n inline-flex items-center gap-1.5 px-4 py-2\n bg-transparent text-[var(--color-text-primary)]\n border border-[var(--color-border-default)]\n rounded-lg\n hover:bg-[var(--color-status-error-bgSubtle)] hover:border-[var(--color-status-error-border)] hover:text-[var(--color-status-error-text)]\n active:scale-[0.98]\n transition-all duration-200\n text-sm font-medium\n disabled:opacity-50 disabled:cursor-not-allowed\n \"\n >\n {rejecting ? <Loader2 size={14} className=\"animate-spin\" /> : <X size={14} />}\n {tr('members.myInvites.decline', 'Decline')}\n </button>\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;AAwBA,SAAgB,EAAa,EAC3B,WACA,aACA,aACA,cACA,gBACoB;CACpB,IAAM,IAAK,GAAO,EACZ,IAAkB,KAAK,IAC3B,GACA,KAAK,MAAM,IAAI,KAAK,EAAO,UAAU,CAAC,SAAS,GAAG,KAAK,KAAK,KAAK,MAAO,KAAK,KAAK,IAAI,CACvF;AAED,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CAEE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD;MAAW,MAAM;MAAI,WAAU;MAAuC,CAAA;KAClE,CAAA,EACN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAO,cAAc,QAAQ,EAAG,uBAAuB,eAAe;OACpE,CAAA;MACL,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAiB,MAAM,EAAO,MAAQ,CAAA,EACtC,kBAAC,QAAD;QAAM,WAAU;kBAAhB,CACE,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,EAClB,IAAkB,IACf,GAAG,EAAG,+BAA+B,aAAa,CAAC,GAAG,EAAgB,KACtE,EAAG,kCAAkC,gBAAgB,CACpD;UACH;;MACL,EAAO,WACN,kBAAC,KAAD;OAAG,WAAU;iBAAb;QAAsE;QAC5D,EAAO;QAAQ;QACrB;;MAEF;OACF;OAGN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,UAAD;KAAQ,MAAK;KACX,eAAe,EAAS,EAAO,GAAG;KAClC,UAAU,KAAa;KACvB,WAAU;eAHZ,CAaG,IAAY,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAO,MAAM,IAAM,CAAA,EAChF,EAAG,4BAA4B,SAAS,CAClC;QACT,kBAAC,UAAD;KAAQ,MAAK;KACX,eAAe,EAAS,EAAO,GAAG;KAClC,UAAU,KAAa;KACvB,WAAU;eAHZ,CAeG,IAAY,kBAAC,GAAD;MAAS,MAAM;MAAI,WAAU;MAAiB,CAAA,GAAG,kBAAC,GAAD,EAAG,MAAM,IAAM,CAAA,EAC5E,EAAG,6BAA6B,UAAU,CACpC;OACL;MACF;;EACF,CAAA"}
|
|
@@ -116,6 +116,7 @@ function h({ organizationId: h }) {
|
|
|
116
116
|
children: /* @__PURE__ */ m("div", {
|
|
117
117
|
className: "flex items-center justify-end gap-1",
|
|
118
118
|
children: [/* @__PURE__ */ p("button", {
|
|
119
|
+
type: "button",
|
|
119
120
|
onClick: () => O(e.id, e.email),
|
|
120
121
|
disabled: w,
|
|
121
122
|
className: "p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors",
|
|
@@ -123,6 +124,7 @@ function h({ organizationId: h }) {
|
|
|
123
124
|
"aria-label": `${g("members.invite.resendFor", "Resend invite for")} ${e.email}`,
|
|
124
125
|
children: /* @__PURE__ */ p(d, { size: 14 })
|
|
125
126
|
}), /* @__PURE__ */ p("button", {
|
|
127
|
+
type: "button",
|
|
126
128
|
onClick: () => D(e.id, e.email),
|
|
127
129
|
disabled: S,
|
|
128
130
|
className: "p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PendingInvitesList.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/PendingInvitesList.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { RotateCw, X, Loader2, AlertCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useOrganizationInvites, useCancelInvite, useResendInvite } from '../hooks/useInvites';\nimport { InviteStatusBadge } from './InviteStatusBadge';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface PendingInvitesListProps {\n organizationId: string;\n}\n\nexport function PendingInvitesList({ organizationId }: PendingInvitesListProps) {\n const tr = useTr();\n const { invites, loading, error, total } = useOrganizationInvites(\n organizationId,\n { status: InviteStatus.Pending },\n 50\n );\n const { cancelInvite, loading: cancelling } = useCancelInvite();\n const { resendInvite, loading: resending } = useResendInvite();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (loading && invites.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 size={20} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <p className=\"py-6 text-center text-sm text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n );\n }\n\n if (invites.length === 0) {\n return (\n <p className=\"text-sm text-[var(--color-text-muted)] text-center py-6\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </p>\n );\n }\n\n const handleCancel = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await cancelInvite(inviteId);\n toast.success(`${tr('members.invite.cancelSuccess', 'Invite cancelled for')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.cancelFailed', 'Failed to cancel invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n const handleResend = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await resendInvite(inviteId);\n toast.success(`${tr('members.invite.resendSuccess', 'Invite re-sent to')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.resendFailed', 'Failed to resend invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n return (\n <div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider mb-3\">\n {tr('members.invite.pendingTitle', 'Pending Invites')} ({total})\n </h3>\n\n {actionError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-2.5\">\n <AlertCircle size={14} 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 )}\n\n <div className=\"overflow-x-auto rounded-lg border border-[var(--color-border-default)]\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-[var(--color-bg-subtle)] border-b border-[var(--color-border-default)]\">\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.emailLabel', 'Email Address')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.status', 'Status')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.expires', 'Expires')}\n </th>\n <th className=\"text-right px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {invites.map((invite) => (\n <tr\n key={invite.id}\n className=\"border-b border-[var(--color-border-default)] last:border-b-0 hover:bg-[var(--color-bg-subtle)] transition-colors\"\n >\n <td className=\"px-4 py-3 text-[var(--color-text-primary)]\">{invite.email}</td>\n <td className=\"px-4 py-3\">\n <MemberRoleBadge role={invite.role} />\n </td>\n <td className=\"px-4 py-3\">\n <InviteStatusBadge status={invite.status} />\n </td>\n <td className=\"px-4 py-3 text-[var(--color-text-muted)]\">\n {new Date(invite.expiresAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <div className=\"flex items-center justify-end gap-1\">\n <button\n onClick={() => handleResend(invite.id, invite.email)}\n disabled={resending}\n className=\"p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors\"\n title={tr('members.invite.resend', 'Resend invite')}\n aria-label={`${tr('members.invite.resendFor', 'Resend invite for')} ${invite.email}`}\n >\n <RotateCw size={14} />\n </button>\n <button\n onClick={() => handleCancel(invite.id, invite.email)}\n disabled={cancelling}\n className=\"p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]\"\n title={tr('members.invite.cancel', 'Cancel invite')}\n aria-label={`${tr('members.invite.cancelFor', 'Cancel invite for')} ${invite.email}`}\n >\n <X size={14} />\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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
|
|
1
|
+
{"version":3,"file":"PendingInvitesList.js","names":[],"sources":["../../../../../src/organization-management/modules/members/components/PendingInvitesList.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { RotateCw, X, Loader2, AlertCircle } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { useOrganizationInvites, useCancelInvite, useResendInvite } from '../hooks/useInvites';\nimport { InviteStatusBadge } from './InviteStatusBadge';\nimport { MemberRoleBadge } from './MemberRoleBadge';\nimport { InviteStatus } from '../../../../generated/global-types';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface PendingInvitesListProps {\n organizationId: string;\n}\n\nexport function PendingInvitesList({ organizationId }: PendingInvitesListProps) {\n const tr = useTr();\n const { invites, loading, error, total } = useOrganizationInvites(\n organizationId,\n { status: InviteStatus.Pending },\n 50\n );\n const { cancelInvite, loading: cancelling } = useCancelInvite();\n const { resendInvite, loading: resending } = useResendInvite();\n const [actionError, setActionError] = useState<string | null>(null);\n\n if (loading && invites.length === 0) {\n return (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 size={20} className=\"animate-spin text-[var(--color-text-muted)]\" />\n </div>\n );\n }\n\n if (error) {\n return (\n <p className=\"py-6 text-center text-sm text-[var(--color-status-error-text)]\">\n {tr('members.myInvites.loadFailed', 'Failed to load invites')}: {error.message}\n </p>\n );\n }\n\n if (invites.length === 0) {\n return (\n <p className=\"text-sm text-[var(--color-text-muted)] text-center py-6\">\n {tr('members.myInvites.emptyTitle', 'No pending invites')}\n </p>\n );\n }\n\n const handleCancel = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await cancelInvite(inviteId);\n toast.success(`${tr('members.invite.cancelSuccess', 'Invite cancelled for')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.cancelFailed', 'Failed to cancel invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n const handleResend = async (inviteId: string, email: string) => {\n try {\n setActionError(null);\n await resendInvite(inviteId);\n toast.success(`${tr('members.invite.resendSuccess', 'Invite re-sent to')} ${email}`);\n } catch (err) {\n const msg =\n err instanceof Error\n ? err.message\n : tr('members.invite.resendFailed', 'Failed to resend invite');\n setActionError(msg);\n toast.error(msg);\n }\n };\n\n return (\n <div>\n <h3 className=\"text-sm font-semibold text-[var(--color-text-secondary)] uppercase tracking-wider mb-3\">\n {tr('members.invite.pendingTitle', 'Pending Invites')} ({total})\n </h3>\n\n {actionError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-2.5\">\n <AlertCircle size={14} 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 )}\n\n <div className=\"overflow-x-auto rounded-lg border border-[var(--color-border-default)]\">\n <table className=\"w-full text-sm\">\n <thead>\n <tr className=\"bg-[var(--color-bg-subtle)] border-b border-[var(--color-border-default)]\">\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.emailLabel', 'Email Address')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.status', 'Status')}\n </th>\n <th className=\"text-left px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('members.invite.expires', 'Expires')}\n </th>\n <th className=\"text-right px-4 py-2.5 font-medium text-[var(--color-text-secondary)]\">\n {tr('common.actions', 'Actions')}\n </th>\n </tr>\n </thead>\n <tbody>\n {invites.map((invite) => (\n <tr\n key={invite.id}\n className=\"border-b border-[var(--color-border-default)] last:border-b-0 hover:bg-[var(--color-bg-subtle)] transition-colors\"\n >\n <td className=\"px-4 py-3 text-[var(--color-text-primary)]\">{invite.email}</td>\n <td className=\"px-4 py-3\">\n <MemberRoleBadge role={invite.role} />\n </td>\n <td className=\"px-4 py-3\">\n <InviteStatusBadge status={invite.status} />\n </td>\n <td className=\"px-4 py-3 text-[var(--color-text-muted)]\">\n {new Date(invite.expiresAt).toLocaleDateString()}\n </td>\n <td className=\"px-4 py-3 text-right\">\n <div className=\"flex items-center justify-end gap-1\">\n <button type=\"button\"\n onClick={() => handleResend(invite.id, invite.email)}\n disabled={resending}\n className=\"p-1.5 rounded-md hover:bg-[var(--color-bg-subtle)] text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] transition-colors\"\n title={tr('members.invite.resend', 'Resend invite')}\n aria-label={`${tr('members.invite.resendFor', 'Resend invite for')} ${invite.email}`}\n >\n <RotateCw size={14} />\n </button>\n <button type=\"button\"\n onClick={() => handleCancel(invite.id, invite.email)}\n disabled={cancelling}\n className=\"p-1.5 rounded-md text-[var(--color-text-muted)] transition-colors hover:bg-[var(--color-status-error-bgSubtle)] hover:text-[var(--color-status-error-text)]\"\n title={tr('members.invite.cancel', 'Cancel invite')}\n aria-label={`${tr('members.invite.cancelFor', 'Cancel invite for')} ${invite.email}`}\n >\n <X size={14} />\n </button>\n </div>\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -28,6 +28,7 @@ function m() {
|
|
|
28
28
|
/* @__PURE__ */ f("div", {
|
|
29
29
|
className: "flex items-center gap-3",
|
|
30
30
|
children: [/* @__PURE__ */ d("button", {
|
|
31
|
+
type: "button",
|
|
31
32
|
onClick: w,
|
|
32
33
|
className: "rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]",
|
|
33
34
|
"aria-label": m("members.invite.backToMembers", "Back to members"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteMemberPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/InviteMemberPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useInviteMember } from '../hooks/useInvites';\nimport { InviteMemberForm } from '../components/InviteMemberForm';\nimport { PendingInvitesList } from '../components/PendingInvitesList';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/**\n * Invite Member Page\n * Email-based invite form with pending invites list below\n */\nexport function InviteMemberPage() {\n const tr = useTr();\n const { organizationId } = useParams<{ organizationId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { inviteMember, loading, error } = useInviteMember();\n\n const handleSubmit = async (data: {\n organizationId: string;\n email: string;\n message?: string;\n }) => {\n await inviteMember(data);\n toast.success(`${tr('members.invite.success', 'Invitation sent to')} ${data.email}`);\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${organizationId}/members`);\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.MEMBER_INVITE)) {\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('members.invite.noPermission', \"You don't have permission to invite members.\")}\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=\"mx-auto max-w-3xl space-y-8\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n <button\n onClick={handleCancel}\n className=\"rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n aria-label={tr('members.invite.backToMembers', 'Back to members')}\n >\n <ArrowLeft size={20} className=\"text-[var(--color-text-secondary)]\" />\n </button>\n <div className=\"flex items-center gap-2\">\n <UserPlus size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.invite.title', 'Invite Member')}\n </h1>\n </div>\n </div>\n\n {/* Invite Form Card */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-4\">\n {tr('members.invite.send', 'Send Invite')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-muted)] mb-6\">\n {tr(\n 'members.invite.description',\n 'Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.'\n )}\n </p>\n\n {error && (\n <div className=\"mb-4 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error.message.includes('CONFLICT')\n ? tr(\n 'members.invite.error.conflict',\n 'An invite already exists for this email address.'\n )\n : error.message.includes('already a member')\n ? tr(\n 'members.invite.error.alreadyMember',\n 'This user is already a member of the organization.'\n )\n : error.message}\n </p>\n </div>\n )}\n\n <InviteMemberForm\n organizationId={organizationId!}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n\n {/* Pending Invites */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <PendingInvitesList organizationId={organizationId!} />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,SAAgB,IAAmB;CACjC,IAAM,IAAK,GAAO,EACZ,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,YAAS,aAAU,GAAiB,EAEpD,IAAe,OAAO,MAItB;AAEJ,EADA,MAAM,EAAa,EAAK,EACxB,EAAM,QAAQ,GAAG,EAAG,0BAA0B,qBAAqB,CAAC,GAAG,EAAK,QAAQ;IAGhF,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,EAAe,UAAU;;AAqBnD,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,cAAc,GAW3C,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;
|
|
1
|
+
{"version":3,"file":"InviteMemberPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/InviteMemberPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft, UserPlus } from 'lucide-react';\nimport { toast } from 'sonner';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useInviteMember } from '../hooks/useInvites';\nimport { InviteMemberForm } from '../components/InviteMemberForm';\nimport { PendingInvitesList } from '../components/PendingInvitesList';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/**\n * Invite Member Page\n * Email-based invite form with pending invites list below\n */\nexport function InviteMemberPage() {\n const tr = useTr();\n const { organizationId } = useParams<{ organizationId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { inviteMember, loading, error } = useInviteMember();\n\n const handleSubmit = async (data: {\n organizationId: string;\n email: string;\n message?: string;\n }) => {\n await inviteMember(data);\n toast.success(`${tr('members.invite.success', 'Invitation sent to')} ${data.email}`);\n };\n\n const handleCancel = () => {\n navigate(`${basePath}/${organizationId}/members`);\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.MEMBER_INVITE)) {\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('members.invite.noPermission', \"You don't have permission to invite members.\")}\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=\"mx-auto max-w-3xl space-y-8\">\n {/* Header */}\n <div className=\"flex items-center gap-3\">\n <button type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg p-2 transition-colors hover:bg-[var(--color-bg-subtle)]\"\n aria-label={tr('members.invite.backToMembers', 'Back to members')}\n >\n <ArrowLeft size={20} className=\"text-[var(--color-text-secondary)]\" />\n </button>\n <div className=\"flex items-center gap-2\">\n <UserPlus size={24} className=\"text-[var(--color-text-primary)]\" />\n <h1 className=\"text-2xl font-bold text-[var(--color-text-primary)]\">\n {tr('members.invite.title', 'Invite Member')}\n </h1>\n </div>\n </div>\n\n {/* Invite Form Card */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-4\">\n {tr('members.invite.send', 'Send Invite')}\n </h2>\n <p className=\"text-sm text-[var(--color-text-muted)] mb-6\">\n {tr(\n 'members.invite.description',\n 'Enter the email address of the person you want to invite. They will receive an email notification and can accept or decline from their account.'\n )}\n </p>\n\n {error && (\n <div className=\"mb-4 rounded-lg border border-[var(--color-status-error-border)] bg-[var(--color-status-error-bgSubtle)] p-3\">\n <p className=\"text-sm text-[var(--color-status-error-text)]\">\n {error.message.includes('CONFLICT')\n ? tr(\n 'members.invite.error.conflict',\n 'An invite already exists for this email address.'\n )\n : error.message.includes('already a member')\n ? tr(\n 'members.invite.error.alreadyMember',\n 'This user is already a member of the organization.'\n )\n : error.message}\n </p>\n </div>\n )}\n\n <InviteMemberForm\n organizationId={organizationId!}\n onSubmit={handleSubmit}\n onCancel={handleCancel}\n loading={loading}\n />\n </div>\n\n {/* Pending Invites */}\n <div className=\"bg-[var(--color-bg-surface)] rounded-xl border border-[var(--color-border-default)] p-6\">\n <PendingInvitesList organizationId={organizationId!} />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAgBA,SAAgB,IAAmB;CACjC,IAAM,IAAK,GAAO,EACZ,EAAE,sBAAmB,GAAuC,EAC5D,EAAE,aAAU,cAAW,qBAAqB,GAAe,EAC3D,EAAE,kBAAe,WAAW,MAAuB,GAAgB,EACnE,EAAE,iBAAc,YAAS,aAAU,GAAiB,EAEpD,IAAe,OAAO,MAItB;AAEJ,EADA,MAAM,EAAa,EAAK,EACxB,EAAM,QAAQ,GAAG,EAAG,0BAA0B,qBAAqB,CAAC,GAAG,EAAK,QAAQ;IAGhF,UAAqB;AACzB,IAAS,GAAG,EAAS,GAAG,EAAe,UAAU;;AAqBnD,QAlBI,IAEA,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,GAAD,EAAc,SAAS,EAAG,6BAA6B,yBAAyB,EAAI,CAAA;EAChF,CAAA,GAIL,EAAc,EAAY,cAAc,GAW3C,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf;IAEE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,UAAD;MAAQ,MAAK;MACX,SAAS;MACT,WAAU;MACV,cAAY,EAAG,gCAAgC,kBAAkB;gBAEjE,kBAAC,GAAD;OAAW,MAAM;OAAI,WAAU;OAAuC,CAAA;MAC/D,CAAA,EACT,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD;OAAU,MAAM;OAAI,WAAU;OAAqC,CAAA,EACnE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,wBAAwB,gBAAgB;OACzC,CAAA,CACD;QACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBACX,EAAG,uBAAuB,cAAc;OACtC,CAAA;MACL,kBAAC,KAAD;OAAG,WAAU;iBACV,EACC,8BACA,kJACD;OACC,CAAA;MAEH,KACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAM,QAAQ,SAAS,WAAW,GAC/B,EACE,iCACA,mDACD,GACD,EAAM,QAAQ,SAAS,mBAAmB,GACxC,EACE,sCACA,qDACD,GACD,EAAM;QACV,CAAA;OACA,CAAA;MAGR,kBAAC,GAAD;OACkB;OAChB,UAAU;OACV,UAAU;OACD;OACT,CAAA;MACE;;IAGN,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAAoC,mBAAmB,CAAA;KACnD,CAAA;IACF;;EACF,CAAA,GAvEJ,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aACZ,EAAG,+BAA+B,+CAA+C;GAC9E,CAAA;EACF,CAAA"}
|
|
@@ -32,13 +32,14 @@ function p() {
|
|
|
32
32
|
children: [/* @__PURE__ */ d("div", {
|
|
33
33
|
className: "mb-6",
|
|
34
34
|
children: [/* @__PURE__ */ d("button", {
|
|
35
|
+
type: "button",
|
|
35
36
|
onClick: () => g(`${_}/${m}/members`),
|
|
36
37
|
className: "inline-flex items-center gap-2 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] active:scale-[0.98] transition-all duration-200 mb-4",
|
|
37
38
|
children: [/* @__PURE__ */ u(s, { size: 20 }), p("members.detail.backToMembers", "Back to Members")]
|
|
38
39
|
}), /* @__PURE__ */ d("div", {
|
|
39
40
|
className: "flex items-start gap-4",
|
|
40
41
|
children: [/* @__PURE__ */ u("div", {
|
|
41
|
-
className: "
|
|
42
|
+
className: "size-16 rounded-full bg-[var(--color-status-info-muted)] flex items-center justify-center",
|
|
42
43
|
children: /* @__PURE__ */ u(c, {
|
|
43
44
|
size: 32,
|
|
44
45
|
className: "text-[var(--color-status-info)]"
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MemberDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MemberDetailPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft, Shield } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useMember } from '../hooks/useMembers';\nimport { MemberRoleBadge } from '../components/MemberRoleBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/**\n * Member Detail Page\n * Simplified version showing available member fields\n */\nexport function MemberDetailPage() {\n const tr = useTr();\n const { organizationId, userId } = useParams<{ organizationId: string; userId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { member, loading, error } = useMember(organizationId!, userId!);\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.MEMBER_READ)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.detail.noPermission', \"You don't have permission to view member details.\")}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState message={tr('members.detail.loading', 'Loading member...')} />\n </div>\n );\n }\n\n if (error || !member) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={error?.message || tr('members.detail.notFound', 'Member not found')}\n action={{\n label: tr('common.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${organizationId}/members`),\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n {/* Header */}\n <div className=\"mb-6\">\n <button\n onClick={() => navigate(`${basePath}/${organizationId}/members`)}\n className=\"inline-flex items-center gap-2 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] active:scale-[0.98] transition-all duration-200 mb-4\"\n >\n <ArrowLeft size={20} />\n {tr('members.detail.backToMembers', 'Back to Members')}\n </button>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"
|
|
1
|
+
{"version":3,"file":"MemberDetailPage.js","names":[],"sources":["../../../../../src/organization-management/modules/members/pages/MemberDetailPage.tsx"],"sourcesContent":["import { useParams } from 'react-router-dom';\nimport { ArrowLeft, Shield } from 'lucide-react';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useOrgConsole } from '../../../providers/OrgConsoleProvider';\nimport { useMember } from '../hooks/useMembers';\nimport { MemberRoleBadge } from '../components/MemberRoleBadge';\nimport { LoadingState } from '../../../shared/components/feedback/LoadingState';\nimport { ErrorState } from '../../../shared/components/feedback/ErrorState';\nimport { PERMISSIONS } from '../../../constants/permissions';\nimport { useTr } from '../../../shared/hooks/useTr';\n\n/**\n * Member Detail Page\n * Simplified version showing available member fields\n */\nexport function MemberDetailPage() {\n const tr = useTr();\n const { organizationId, userId } = useParams<{ organizationId: string; userId: string }>();\n const { navigate, basePath = '/organizations' } = useOrgConsole();\n const { hasPermission, isLoading: permissionsLoading } = usePermissions();\n const { member, loading, error } = useMember(organizationId!, userId!);\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.MEMBER_READ)) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] p-6\">\n <div className=\"p-8 text-center text-[var(--color-text-secondary)]\">\n {tr('members.detail.noPermission', \"You don't have permission to view member details.\")}\n </div>\n </div>\n );\n }\n\n if (loading) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <LoadingState message={tr('members.detail.loading', 'Loading member...')} />\n </div>\n );\n }\n\n if (error || !member) {\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6\">\n <ErrorState\n message={error?.message || tr('members.detail.notFound', 'Member not found')}\n action={{\n label: tr('common.goBack', 'Go Back'),\n onClick: () => navigate(`${basePath}/${organizationId}/members`),\n }}\n />\n </div>\n );\n }\n\n return (\n <div className=\"min-h-full bg-[var(--color-bg-canvas)] px-4 py-6 sm:px-6 animate-in fade-in duration-300\">\n {/* Header */}\n <div className=\"mb-6\">\n <button type=\"button\"\n onClick={() => navigate(`${basePath}/${organizationId}/members`)}\n className=\"inline-flex items-center gap-2 text-[var(--color-text-secondary)] hover:text-[var(--color-text-primary)] active:scale-[0.98] transition-all duration-200 mb-4\"\n >\n <ArrowLeft size={20} />\n {tr('members.detail.backToMembers', 'Back to Members')}\n </button>\n\n <div className=\"flex items-start gap-4\">\n <div className=\"size-16 rounded-full bg-[var(--color-status-info-muted)] flex items-center justify-center\">\n <Shield size={32} className=\"text-[var(--color-status-info)]\" />\n </div>\n <div>\n <h1 className=\"text-3xl font-bold text-[var(--color-text-primary)]\">{member.userId}</h1>\n <p className=\"text-[var(--color-text-secondary)] mt-1\">\n {tr('members.list.memberId', 'Member ID')}: {member.id}\n </p>\n </div>\n </div>\n </div>\n\n {/* Details */}\n <div className=\"bg-[var(--color-bg-surface)] border border-[var(--color-border-default)] rounded-lg p-6\">\n <h2 className=\"text-lg font-semibold text-[var(--color-text-primary)] mb-4\">\n {tr('members.detail.info', 'Member Information')}\n </h2>\n\n <div className=\"space-y-4\">\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr('members.detail.userId', 'User ID')}\n </p>\n <p className=\"text-[var(--color-text-primary)] font-medium\">{member.userId}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr('members.detail.orgId', 'Organization ID')}\n </p>\n <p className=\"text-[var(--color-text-primary)] font-medium\">{member.organizationId}</p>\n </div>\n\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr('members.list.column.role', 'Role')}\n </p>\n <div className=\"mt-1\">\n <MemberRoleBadge role={member.role} />\n </div>\n </div>\n\n {member.invitedBy && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr('members.detail.invitedBy', 'Invited By')}\n </p>\n <p className=\"text-[var(--color-text-primary)] font-medium\">{member.invitedBy}</p>\n </div>\n )}\n\n {member.joinedAt && (\n <div>\n <p className=\"text-sm text-[var(--color-text-secondary)]\">\n {tr('members.detail.joinedAt', 'Joined At')}\n </p>\n <p className=\"text-[var(--color-text-primary)] font-medium\">\n {new Date(member.joinedAt).toLocaleString()}\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -112,6 +112,7 @@ function T() {
|
|
|
112
112
|
children: T("members.list.subtitle", "Manage organization members, inspect their roles, and invite more people into this organization.")
|
|
113
113
|
})]
|
|
114
114
|
}), M && k(l.MEMBER_INVITE) && /* @__PURE__ */ S("button", {
|
|
115
|
+
type: "button",
|
|
115
116
|
onClick: () => D(`${O}/${N}/members/invite`),
|
|
116
117
|
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]",
|
|
117
118
|
children: [/* @__PURE__ */ x(g, {
|
|
@@ -135,6 +136,7 @@ function T() {
|
|
|
135
136
|
inputId: "organization-members-search",
|
|
136
137
|
className: "flex-1"
|
|
137
138
|
}), /* @__PURE__ */ x("button", {
|
|
139
|
+
type: "button",
|
|
138
140
|
onClick: () => {
|
|
139
141
|
U();
|
|
140
142
|
},
|
|
@@ -159,6 +161,7 @@ function T() {
|
|
|
159
161
|
ariaLabel: T("members.list.selectAll", "Select all members")
|
|
160
162
|
}), /* @__PURE__ */ x("span", { children: T("common.selectAllVisible", "Select all visible") })]
|
|
161
163
|
}), I.size > 0 ? /* @__PURE__ */ x("button", {
|
|
164
|
+
type: "button",
|
|
162
165
|
onClick: () => L(/* @__PURE__ */ new Set()),
|
|
163
166
|
className: "text-sm text-[var(--color-text-secondary)] transition-colors hover:text-[var(--color-text-primary)]",
|
|
164
167
|
children: T("common.clearSelection", "Clear selection")
|
|
@@ -173,6 +176,7 @@ function T() {
|
|
|
173
176
|
T("common.selected", "selected")
|
|
174
177
|
]
|
|
175
178
|
}), k(l.MEMBER_REMOVE) && /* @__PURE__ */ S("button", {
|
|
179
|
+
type: "button",
|
|
176
180
|
onClick: ae,
|
|
177
181
|
disabled: q.length === 0 || G,
|
|
178
182
|
className: "inline-flex min-h-11 items-center justify-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50",
|
|
@@ -219,7 +223,7 @@ function T() {
|
|
|
219
223
|
}), /* @__PURE__ */ S("div", {
|
|
220
224
|
className: "flex min-w-0 flex-1 gap-3",
|
|
221
225
|
children: [/* @__PURE__ */ x("div", {
|
|
222
|
-
className: "flex
|
|
226
|
+
className: "flex size-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]",
|
|
223
227
|
children: /* @__PURE__ */ x(v, {
|
|
224
228
|
size: 18,
|
|
225
229
|
"aria-hidden": "true"
|
|
@@ -260,6 +264,7 @@ function T() {
|
|
|
260
264
|
}), /* @__PURE__ */ S("div", {
|
|
261
265
|
className: "mt-4 flex flex-wrap gap-2 border-t border-[var(--color-border-default)] pt-4",
|
|
262
266
|
children: [/* @__PURE__ */ S("button", {
|
|
267
|
+
type: "button",
|
|
263
268
|
onClick: () => D(`${O}/${N}/members/${e.userId}`),
|
|
264
269
|
className: "inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-border-default)] px-3 py-2 text-sm text-[var(--color-text-primary)] transition-all duration-200 hover:bg-[var(--color-bg-subtle)]",
|
|
265
270
|
children: [/* @__PURE__ */ x(h, {
|
|
@@ -267,6 +272,7 @@ function T() {
|
|
|
267
272
|
"aria-hidden": "true"
|
|
268
273
|
}), T("common.view", "View")]
|
|
269
274
|
}), k(l.MEMBER_REMOVE) && /* @__PURE__ */ S("button", {
|
|
275
|
+
type: "button",
|
|
270
276
|
onClick: () => Z(e.userId),
|
|
271
277
|
disabled: G,
|
|
272
278
|
className: "inline-flex min-h-10 items-center gap-2 rounded-lg border border-[var(--color-status-error-border)] px-3 py-2 text-sm text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50",
|
|
@@ -334,7 +340,7 @@ function T() {
|
|
|
334
340
|
children: /* @__PURE__ */ S("div", {
|
|
335
341
|
className: "flex items-start gap-3",
|
|
336
342
|
children: [/* @__PURE__ */ x("div", {
|
|
337
|
-
className: "flex
|
|
343
|
+
className: "flex size-10 items-center justify-center rounded-full bg-[var(--color-status-info-bgSubtle)] text-[var(--color-status-info-text)]",
|
|
338
344
|
children: /* @__PURE__ */ x(v, {
|
|
339
345
|
size: 18,
|
|
340
346
|
"aria-hidden": "true"
|
|
@@ -372,6 +378,7 @@ function T() {
|
|
|
372
378
|
children: /* @__PURE__ */ S("div", {
|
|
373
379
|
className: "flex items-center justify-end gap-2",
|
|
374
380
|
children: [/* @__PURE__ */ x("button", {
|
|
381
|
+
type: "button",
|
|
375
382
|
onClick: () => D(`${O}/${N}/members/${e.userId}`),
|
|
376
383
|
className: "rounded-lg p-2 text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-bg-muted)] hover:text-[var(--color-text-primary)]",
|
|
377
384
|
"aria-label": `${T("members.list.viewMember", "View member")} ${e.userId}`,
|
|
@@ -381,6 +388,7 @@ function T() {
|
|
|
381
388
|
"aria-hidden": "true"
|
|
382
389
|
})
|
|
383
390
|
}), k(l.MEMBER_REMOVE) && /* @__PURE__ */ x("button", {
|
|
391
|
+
type: "button",
|
|
384
392
|
onClick: () => Z(e.userId),
|
|
385
393
|
disabled: G,
|
|
386
394
|
className: "rounded-lg p-2 text-[var(--color-status-error-text)] transition-all duration-200 hover:bg-[var(--color-status-error-bgSubtle)] disabled:cursor-not-allowed disabled:opacity-50",
|