From 5b79311117f6cfd3dbb1a88c188205e2b0bd6b04 Mon Sep 17 00:00:00 2001
From: rax7389
Date: Wed, 8 Apr 2026 16:54:15 +0530
Subject: [PATCH 1/5] feat(react, examples): add invitation hook, container,
and example apps
---
.../src/app/member-management/page.tsx | 21 ++
.../src/components/navigation/side-bar.tsx | 11 +-
.../next-rwa/src/providers/i18n-provider.tsx | 1 +
examples/react-spa-npm/src/App.tsx | 9 +
.../react-spa-npm/src/components/side-bar.tsx | 11 +-
.../src/views/member-management-page.tsx | 21 ++
examples/react-spa-shadcn/src/App.tsx | 9 +
.../src/components/side-bar.tsx | 11 +-
.../src/pages/MemberManagement.tsx | 23 ++
examples/scripts/utils/env-writer.mjs | 2 +
examples/scripts/utils/resource-servers.mjs | 2 +
.../organization-member-management.tsx | 244 +++++++++++++++
packages/react/src/components/index.ts | 1 +
packages/react/src/hooks/index.ts | 3 +
.../services/use-member-management-service.ts | 201 +++++++++++++
.../src/hooks/my-organization/use-config.ts | 6 +
.../use-organization-member-management.ts | 282 ++++++++++++++++++
17 files changed, 855 insertions(+), 3 deletions(-)
create mode 100644 examples/next-rwa/src/app/member-management/page.tsx
create mode 100644 examples/react-spa-npm/src/views/member-management-page.tsx
create mode 100644 examples/react-spa-shadcn/src/pages/MemberManagement.tsx
create mode 100644 packages/react/src/components/auth0/my-organization/organization-member-management.tsx
create mode 100644 packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
create mode 100644 packages/react/src/hooks/my-organization/use-organization-member-management.ts
diff --git a/examples/next-rwa/src/app/member-management/page.tsx b/examples/next-rwa/src/app/member-management/page.tsx
new file mode 100644
index 000000000..7dcc22ec3
--- /dev/null
+++ b/examples/next-rwa/src/app/member-management/page.tsx
@@ -0,0 +1,21 @@
+'use client';
+
+// import { OrganizationMemberManagement } from '@auth0/universal-components-react/rwa';
+
+export default function MemberManagementPage() {
+ return (
+
+ );
+}
diff --git a/examples/next-rwa/src/components/navigation/side-bar.tsx b/examples/next-rwa/src/components/navigation/side-bar.tsx
index e9a45a6f1..0eb2407d3 100644
--- a/examples/next-rwa/src/components/navigation/side-bar.tsx
+++ b/examples/next-rwa/src/components/navigation/side-bar.tsx
@@ -1,7 +1,7 @@
'use client';
import { useUser } from '@auth0/nextjs-auth0';
-import { Building, Settings, Shield, User } from 'lucide-react';
+import { Building, Settings, Shield, User, Users } from 'lucide-react';
import Link from 'next/link';
import React from 'react';
import { useTranslation } from 'react-i18next';
@@ -74,6 +74,15 @@ export const Sidebar: React.FC = () => {
{t('sidebar.identity-providers')}
+
+
+
+ {t('sidebar.members')}
+
+
diff --git a/examples/next-rwa/src/providers/i18n-provider.tsx b/examples/next-rwa/src/providers/i18n-provider.tsx
index 793c0e42d..5312cc407 100644
--- a/examples/next-rwa/src/providers/i18n-provider.tsx
+++ b/examples/next-rwa/src/providers/i18n-provider.tsx
@@ -26,6 +26,7 @@ i18n.use(initReactI18next).init({
'sidebar.organization-settings': 'Organization Settings',
'sidebar.domains': 'Domains',
'sidebar.identity-providers': 'Identity Providers',
+ 'sidebar.members': 'Members',
},
},
},
diff --git a/examples/react-spa-npm/src/App.tsx b/examples/react-spa-npm/src/App.tsx
index 08ec086e1..9b443b784 100644
--- a/examples/react-spa-npm/src/App.tsx
+++ b/examples/react-spa-npm/src/App.tsx
@@ -8,6 +8,7 @@ import { Navbar } from './components/nav-bar';
import { Sidebar } from './components/side-bar';
import DomainManagementPage from './views/domain-management-page';
import HomePage from './views/home-page';
+import MemberManagementPage from './views/member-management-page';
import MFAPage from './views/mfa-page';
import OrganizationManagementPage from './views/organization-management-page';
import ProfilePage from './views/profile-page';
@@ -101,6 +102,14 @@ function AppContent() {
}
/>
+
+
+
+ }
+ />
diff --git a/examples/react-spa-npm/src/components/side-bar.tsx b/examples/react-spa-npm/src/components/side-bar.tsx
index 402398c19..58ab2150e 100644
--- a/examples/react-spa-npm/src/components/side-bar.tsx
+++ b/examples/react-spa-npm/src/components/side-bar.tsx
@@ -1,4 +1,4 @@
-import { User, Building, Settings, Shield } from 'lucide-react';
+import { User, Building, Settings, Shield, Users } from 'lucide-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@@ -70,6 +70,15 @@ export const Sidebar: React.FC = () => {
{t('sidebar.domain-management')}
+
+
+
+ {t('sidebar.member-management')}
+
+
diff --git a/examples/react-spa-npm/src/views/member-management-page.tsx b/examples/react-spa-npm/src/views/member-management-page.tsx
new file mode 100644
index 000000000..e9667f79d
--- /dev/null
+++ b/examples/react-spa-npm/src/views/member-management-page.tsx
@@ -0,0 +1,21 @@
+// import { OrganizationMemberManagement } from '@auth0/universal-components-react/spa';
+
+const MemberManagementPage = () => {
+ return (
+
+ );
+};
+
+export default MemberManagementPage;
diff --git a/examples/react-spa-shadcn/src/App.tsx b/examples/react-spa-shadcn/src/App.tsx
index c39fdede8..f8f142aa2 100644
--- a/examples/react-spa-shadcn/src/App.tsx
+++ b/examples/react-spa-shadcn/src/App.tsx
@@ -10,6 +10,7 @@ import { Sidebar } from './components/side-bar';
import { config } from './config/env';
// import { useDarkMode } from './hooks/use-dark-mode';
import DomainManagement from './pages/DomainManagement';
+import MemberManagement from './pages/MemberManagement';
import IdentityProviderManagement from './pages/IdentityProviderManagement';
import IdentityProviderManagementCreate from './pages/IdentityProviderManagementCreate';
import IdentityProviderManagementEdit from './pages/IdentityProviderManagementEdit';
@@ -136,6 +137,14 @@ const App = () => {
}
/>
+
+
+
+ }
+ />
{/* */}
diff --git a/examples/react-spa-shadcn/src/components/side-bar.tsx b/examples/react-spa-shadcn/src/components/side-bar.tsx
index e0bc08a9b..591c48d73 100644
--- a/examples/react-spa-shadcn/src/components/side-bar.tsx
+++ b/examples/react-spa-shadcn/src/components/side-bar.tsx
@@ -1,4 +1,4 @@
-import { User, Building, Shield, Settings } from 'lucide-react';
+import { User, Building, Shield, Settings, Users } from 'lucide-react';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { Link } from 'react-router-dom';
@@ -70,6 +70,15 @@ export const Sidebar: React.FC = () => {
{t('sidebar.domain-management')}
+
+
+
+ {t('sidebar.member-management')}
+
+
diff --git a/examples/react-spa-shadcn/src/pages/MemberManagement.tsx b/examples/react-spa-shadcn/src/pages/MemberManagement.tsx
new file mode 100644
index 000000000..bd89bdcc3
--- /dev/null
+++ b/examples/react-spa-shadcn/src/pages/MemberManagement.tsx
@@ -0,0 +1,23 @@
+// import { OrganizationMemberManagement } from '@auth0/universal-components-react/spa';
+
+const MemberManagement = () => {
+ return (
+
+ );
+};
+
+export default MemberManagement;
diff --git a/examples/scripts/utils/env-writer.mjs b/examples/scripts/utils/env-writer.mjs
index 9b4d573fd..52eee8c88 100644
--- a/examples/scripts/utils/env-writer.mjs
+++ b/examples/scripts/utils/env-writer.mjs
@@ -72,6 +72,8 @@ const MYORG_SCOPES = [
"delete:my_org:domains",
"create:my_org:domains",
"update:my_org:domains",
+ "read:my_org:member_invitations",
+ "delete:my_org:member_invitations"
]
// My Account API scopes
diff --git a/examples/scripts/utils/resource-servers.mjs b/examples/scripts/utils/resource-servers.mjs
index d7eeb6b91..95fe0c88d 100644
--- a/examples/scripts/utils/resource-servers.mjs
+++ b/examples/scripts/utils/resource-servers.mjs
@@ -26,6 +26,8 @@ export const MYORG_API_SCOPES = [
"read:my_org:identity_providers_provisioning",
"delete:my_org:identity_providers_provisioning",
"read:my_org:configuration",
+"read:my_org:member_invitations",
+"delete:my_org:member_invitations"
]
// My Account API Scopes - desired scopes for MFA management
diff --git a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
new file mode 100644
index 000000000..b5338b493
--- /dev/null
+++ b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
@@ -0,0 +1,244 @@
+/**
+ * Organization member management component.
+ * @module organization-member-management
+ */
+
+import { getComponentStyles } from '@auth0/universal-components-core';
+import { Plus } from 'lucide-react';
+import * as React from 'react';
+
+import { GateKeeper } from '../shared/gate-keeper/gate-keeper';
+
+import { OrganizationInvitationDetailsModal } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-details/organization-invitation-details-modal';
+import { OrganizationInvitationRevokeModal } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-revoke/organization-invitation-revoke-modal';
+import { OrganizationInvitationTable } from '@/components/auth0/my-organization/shared/member-management/invitations/invitation-table/organization-invitation-table';
+import { OrganizationInvitationCreateModal } from '@/components/auth0/my-organization/shared/member-management/shared/invitation-create/organization-invitation-create-modal';
+import { Header } from '@/components/auth0/shared/header';
+import { StyledScope } from '@/components/auth0/shared/styled-scope';
+import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
+import { useOrganizationMemberManagement } from '@/hooks/my-organization/use-organization-member-management';
+import { useTheme } from '@/hooks/shared/use-theme';
+import { useTranslator } from '@/hooks/shared/use-translator';
+import type {
+ OrganizationMemberManagementProps,
+ UseOrganizationMemberManagementResult,
+} from '@/types/my-organization/member-management/organization-member-management-types';
+
+/**
+ * Props for the OrganizationMemberManagementView component.
+ */
+export interface OrganizationMemberManagementViewProps
+ extends UseOrganizationMemberManagementResult {
+ styling: OrganizationMemberManagementProps['styling'];
+ customMessages: OrganizationMemberManagementProps['customMessages'];
+ hideHeader: boolean;
+ readOnly: boolean;
+}
+
+/**
+ * View component for organization member management.
+ * @param props - The component props.
+ * @returns The component.
+ */
+export function OrganizationMemberManagementView(props: OrganizationMemberManagementViewProps) {
+ const {
+ styling,
+ customMessages,
+ hideHeader,
+ readOnly,
+ activeTab,
+ invitations,
+ isFetchingInvitations,
+ isCreatingInvitation,
+ isRevokingInvitation,
+ isResendingInvitation,
+ invitationPagination,
+ invitationFilters,
+ invitationSortConfig,
+ availableRoles,
+ availableProviders,
+ showCreateModal,
+ showDetailsModal,
+ showRevokeModal,
+ showRevokeResendModal,
+ selectedInvitation,
+ setActiveTab,
+ handleCreateClick,
+ handleCreateSubmit,
+ handleCreateCancel,
+ handleDetailsClick,
+ handleDetailsClose,
+ handleRevokeClick,
+ handleRevokeConfirm,
+ handleRevokeCancel,
+ handleRevokeResendClick,
+ handleRevokeResendConfirm,
+ handleRevokeResendCancel,
+ handleCopyUrl,
+ handleSortChange,
+ handleNextPage,
+ handlePreviousPage,
+ handlePageSizeChange,
+ handleRoleFilterChange,
+ } = props;
+
+ const { isDarkMode } = useTheme();
+ const { t } = useTranslator('member_management', customMessages as Record);
+
+ const currentStyles = React.useMemo(
+ () => getComponentStyles(styling, isDarkMode),
+ [styling, isDarkMode],
+ );
+
+ return (
+
+
+ {!hideHeader && (
+
+
+
+ )}
+
+
setActiveTab(value as 'members' | 'invitations')}
+ className={currentStyles.classes?.['OrganizationMemberManagement-tabs']}
+ >
+
+ {t('tabs.members')}
+ {t('tabs.invitations')}
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+
invitation && handleRevokeClick(invitation)}
+ onResend={(invitation) => invitation && handleRevokeResendClick(invitation)}
+ className={currentStyles.classes?.['OrganizationInvitationTab-detailsModal']}
+ />
+
+ handleRevokeConfirm()}
+ className={currentStyles.classes?.['OrganizationInvitationTab-revokeModal']}
+ />
+
+ handleRevokeResendConfirm()}
+ className={currentStyles.classes?.['OrganizationInvitationTab-revokeResendModal']}
+ />
+
+
+ );
+}
+
+/**
+ * Container component for organization member management.
+ * @param props - The component props.
+ * @returns The component.
+ */
+export function OrganizationMemberManagement(props: OrganizationMemberManagementProps) {
+ const {
+ hideHeader = false,
+ customMessages = {},
+ styling = { variables: { common: {}, light: {}, dark: {} }, classes: {} },
+ readOnly = false,
+ createInvitationAction,
+ revokeInvitationAction,
+ resendInvitationAction,
+ } = props;
+
+ const memberManagement = useOrganizationMemberManagement({
+ customMessages,
+ readOnly,
+ createInvitationAction,
+ revokeInvitationAction,
+ resendInvitationAction,
+ });
+
+ return (
+
+
+
+ );
+}
diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts
index 9b0ccceee..e57cc966d 100644
--- a/packages/react/src/components/index.ts
+++ b/packages/react/src/components/index.ts
@@ -12,6 +12,7 @@ export {
} from './auth0/my-organization/sso-provider-create';
export { SsoProviderTable, SsoProviderTableView } from './auth0/my-organization/sso-provider-table';
export { DomainTable, DomainTableView } from './auth0/my-organization/domain-table';
+export { OrganizationMemberManagement } from './auth0/my-organization/organization-member-management';
export {
OrganizationDetailsEdit,
OrganizationDetailsEditView,
diff --git a/packages/react/src/hooks/index.ts b/packages/react/src/hooks/index.ts
index 646b4e008..f32bcfe11 100644
--- a/packages/react/src/hooks/index.ts
+++ b/packages/react/src/hooks/index.ts
@@ -28,3 +28,6 @@ export { useSsoDomainTab } from './my-organization/use-sso-domain-tab';
export { useSsoProviderCreate } from './my-organization/use-sso-provider-create';
export { useSsoProviderEdit } from './my-organization/use-sso-provider-edit';
export { useSsoProviderTable } from './my-organization/use-sso-provider-table';
+
+// Member Management hooks
+export { useOrganizationMemberManagement } from './my-organization/use-organization-member-management';
diff --git a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
new file mode 100644
index 000000000..3adbec2a8
--- /dev/null
+++ b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
@@ -0,0 +1,201 @@
+/**
+ * Member management service hook.
+ * @module use-member-management-service
+ * @internal
+ */
+
+import {
+ type MemberInvitation,
+ type ListIdentityProvidersResponseContent,
+} from '@auth0/universal-components-core';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import * as React from 'react';
+
+import { showToast } from '@/components/auth0/shared/toast';
+import { useCoreClient } from '@/hooks/shared/use-core-client';
+import { useTranslator } from '@/hooks/shared/use-translator';
+import type {
+ CreateInvitationInput,
+ InvitationSortConfig,
+} from '@/types/my-organization/member-management/organization-invitation-table-types';
+import type {
+ UseMemberManagementServiceOptions,
+ MemberManagementServiceResult,
+} from '@/types/my-organization/member-management/organization-member-management-types';
+
+export const memberManagementQueryKeys = {
+ all: ['member-management'] as const,
+ invitations: () => [...memberManagementQueryKeys.all, 'invitations'] as const,
+};
+
+const INVITATION_SORT_FIELD_MAP: Record = {
+ created_at: 'created_at',
+};
+
+/**
+ * Builds a sort parameter string for the API.
+ * @param sortConfig - The sort configuration.
+ * @returns The formatted sort string, or undefined if no valid sort key.
+ */
+function buildSortParam(sortConfig: InvitationSortConfig): string | undefined {
+ if (!sortConfig.key) return undefined;
+ const apiField = INVITATION_SORT_FIELD_MAP[sortConfig.key];
+ if (!apiField) return undefined;
+ const direction = sortConfig.direction === 'asc' ? '1' : '-1';
+ return `${apiField}:${direction}`;
+}
+
+/**
+ * Service hook for member management API operations.
+ * @param options - Service configuration options.
+ * @returns Query and mutation objects for member management.
+ */
+export function useMemberManagementService(
+ options: UseMemberManagementServiceOptions,
+): MemberManagementServiceResult {
+ const {
+ customMessages = {},
+ activeTab,
+ createInvitationAction,
+ revokeInvitationAction,
+ resendInvitationAction,
+ invitationParams,
+ } = options;
+
+ const isInvitationsTabActive = activeTab === 'invitations';
+
+ const { coreClient } = useCoreClient();
+ const { t } = useTranslator('member_management', customMessages as Record);
+ const queryClient = useQueryClient();
+
+ const providersQuery = useQuery({
+ queryKey: [...memberManagementQueryKeys.all, 'identity-providers'],
+ queryFn: async () => {
+ const response: ListIdentityProvidersResponseContent = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.identityProviders.list();
+ const providers = response.identity_providers ?? [];
+ return providers.map((p) => ({
+ id: p.id!,
+ name: p.display_name ?? p.name ?? '',
+ type: p.strategy,
+ }));
+ },
+ enabled: !!coreClient && isInvitationsTabActive,
+ });
+
+ const invitationsQuery = useQuery({
+ queryKey: [
+ ...memberManagementQueryKeys.invitations(),
+ invitationParams.pageSize,
+ invitationParams.fromToken,
+ invitationParams.filters,
+ invitationParams.sortConfig,
+ ],
+ queryFn: async () => {
+ const page = await coreClient!.getMyOrganizationApiClient().organization.invitations.list({
+ take: invitationParams.pageSize,
+ from: invitationParams.fromToken,
+ sort: buildSortParam(invitationParams.sortConfig),
+ });
+
+ const invitations: MemberInvitation[] = page.data;
+ const next = page.response.next ?? null;
+ const total = (page.response as Record).total as number | undefined;
+
+ return { invitations, next, total };
+ },
+ enabled: !!coreClient && isInvitationsTabActive,
+ });
+
+ const createInvitationMutation = useMutation({
+ mutationFn: async (data: CreateInvitationInput) => {
+ if (createInvitationAction?.onBefore && !createInvitationAction.onBefore(data)) {
+ throw new Error('Create action cancelled by onBefore');
+ }
+ const response = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.invitations.create({
+ invitees: [{ email: data.invitee.email, roles: data.roles }],
+ inviter: data.inviter,
+ ttl_sec: data.ttl_sec,
+ });
+ return Array.isArray(response) ? response[0] : response;
+ },
+ onSuccess: (result, data) => {
+ createInvitationAction?.onAfter?.(data, result);
+ showToast({ type: 'success', message: t('invitation.create.success') });
+ queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
+ },
+ onError: () => {
+ showToast({ type: 'error', message: t('invitation.error.create_failed') });
+ },
+ });
+
+ const revokeInvitationMutation = useMutation({
+ mutationFn: async (invitation: MemberInvitation) => {
+ if (revokeInvitationAction?.onBefore && !revokeInvitationAction.onBefore(invitation)) {
+ throw new Error('Revoke action cancelled by onBefore');
+ }
+ await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.invitations.delete(invitation.id!);
+ return invitation;
+ },
+ onSuccess: (invitation) => {
+ revokeInvitationAction?.onAfter?.(invitation);
+ showToast({ type: 'success', message: t('invitation.revoke.success') });
+ queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
+ },
+ onError: () => {
+ showToast({ type: 'error', message: t('invitation.error.revoke_failed') });
+ },
+ });
+
+ const resendInvitationMutation = useMutation({
+ mutationFn: async (invitation: MemberInvitation) => {
+ if (resendInvitationAction?.onBefore && !resendInvitationAction.onBefore(invitation)) {
+ throw new Error('Resend action cancelled by onBefore');
+ }
+ const freshInvitation = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.invitations.get(invitation.id!);
+ await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.invitations.delete(freshInvitation.id ?? invitation.id!);
+ const email = freshInvitation.invitee?.email ?? invitation.invitee?.email ?? '';
+ const roles = freshInvitation.roles ?? invitation.roles;
+ const response = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.invitations.create({
+ invitees: [{ email, roles }],
+ });
+ return Array.isArray(response) ? response[0] : response;
+ },
+ onSuccess: (result, invitation) => {
+ resendInvitationAction?.onAfter?.(invitation, result);
+ showToast({ type: 'success', message: t('invitation.success.invitation_resent') });
+ queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
+ },
+ onError: () => {
+ showToast({ type: 'error', message: t('invitation.error.resend_failed') });
+ queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
+ },
+ });
+
+ const fetchInvitationDetails = React.useCallback(
+ async (invitationId: string): Promise => {
+ return coreClient!.getMyOrganizationApiClient().organization.invitations.get(invitationId);
+ },
+ [coreClient],
+ );
+
+ return {
+ providersQuery,
+ invitationsQuery,
+ createInvitationMutation,
+ revokeInvitationMutation,
+ resendInvitationMutation,
+ fetchInvitationDetails,
+ };
+}
diff --git a/packages/react/src/hooks/my-organization/use-config.ts b/packages/react/src/hooks/my-organization/use-config.ts
index f3db4455f..bf0488256 100644
--- a/packages/react/src/hooks/my-organization/use-config.ts
+++ b/packages/react/src/hooks/my-organization/use-config.ts
@@ -51,6 +51,11 @@ export function useConfig(): UseConfigResult {
const isConfigValid = !!allowedStrategies?.length;
+ const allowedRoles =
+ ((config as Record)?.allowed_roles as
+ | Array<{ id: string; name: string; description?: string }>
+ | undefined) ?? [];
+
return {
config: config ?? null,
isLoadingConfig: configQuery.isLoading,
@@ -58,5 +63,6 @@ export function useConfig(): UseConfigResult {
filteredStrategies,
shouldAllowDeletion,
isConfigValid,
+ allowedRoles,
};
}
diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
new file mode 100644
index 000000000..1af33e6da
--- /dev/null
+++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
@@ -0,0 +1,282 @@
+/**
+ * Organization member management hook.
+ * @module use-organization-member-management
+ */
+
+import { type MemberInvitation } from '@auth0/universal-components-core';
+import * as React from 'react';
+
+import { showToast } from '@/components/auth0/shared/toast';
+import { useMemberManagementService } from '@/hooks/my-organization/shared/services/use-member-management-service';
+import { useConfig } from '@/hooks/my-organization/use-config';
+import { useCheckpointPagination } from '@/hooks/shared/use-checkpoint-pagination';
+import { useTranslator } from '@/hooks/shared/use-translator';
+import type {
+ CreateInvitationInput,
+ InvitationFilterState,
+ InvitationSortConfig,
+ RoleOption,
+ IdentityProviderOption,
+} from '@/types/my-organization/member-management/organization-invitation-table-types';
+import type {
+ ActiveTab,
+ UseOrganizationMemberManagementOptions,
+ UseOrganizationMemberManagementResult,
+} from '@/types/my-organization/member-management/organization-member-management-types';
+
+export { memberManagementQueryKeys } from '@/hooks/my-organization/shared/services/use-member-management-service';
+
+/**
+ * Hook for organization member management.
+ * @param options - Hook configuration options.
+ * @returns State and handler functions.
+ */
+export function useOrganizationMemberManagement(
+ options: UseOrganizationMemberManagementOptions,
+): UseOrganizationMemberManagementResult {
+ const {
+ customMessages = {},
+ readOnly = false,
+ createInvitationAction,
+ revokeInvitationAction,
+ resendInvitationAction,
+ } = options;
+
+ const { t } = useTranslator('member_management', customMessages as Record);
+
+ const [activeTab, setActiveTab] = React.useState('members');
+
+ const { allowedRoles } = useConfig();
+ const availableRoles: RoleOption[] = allowedRoles;
+
+ const {
+ pageSize: invitationPageSize,
+ currentPage: invitationCurrentPage,
+ fromToken: invitationFromToken,
+ hasPreviousPage: invitationHasPreviousPage,
+ sortConfig: invitationSortConfig,
+ filters: invitationFilters,
+ goToNextPage: invitationGoToNextPage,
+ goToPreviousPage: invitationGoToPreviousPage,
+ changePageSize: invitationChangePageSize,
+ changeSortConfig: invitationChangeSortConfig,
+ changeFilters: invitationChangeFilters,
+ } = useCheckpointPagination();
+
+ const [showCreateModal, setShowCreateModal] = React.useState(false);
+ const [showDetailsModal, setShowDetailsModal] = React.useState(false);
+ const [showRevokeModal, setShowRevokeModal] = React.useState(false);
+ const [showRevokeResendModal, setShowRevokeResendModal] = React.useState(false);
+ const [selectedInvitation, setSelectedInvitation] = React.useState(null);
+ const detailsRequestIdRef = React.useRef(0);
+
+ const {
+ providersQuery,
+ invitationsQuery,
+ createInvitationMutation,
+ revokeInvitationMutation,
+ resendInvitationMutation,
+ fetchInvitationDetails,
+ } = useMemberManagementService({
+ customMessages,
+ activeTab,
+ createInvitationAction,
+ revokeInvitationAction,
+ resendInvitationAction,
+ invitationParams: {
+ pageSize: invitationPageSize,
+ fromToken: invitationFromToken,
+ sortConfig: invitationSortConfig,
+ filters: invitationFilters,
+ },
+ });
+
+ const availableProviders: IdentityProviderOption[] = providersQuery.data ?? [];
+ const currentInvitations = invitationsQuery.data?.invitations ?? [];
+ const invitationNextToken = invitationsQuery.data?.next ?? null;
+ const invitationsTotalItems = invitationsQuery.data?.total;
+
+ const handleCreateClick = React.useCallback(() => {
+ if (readOnly) return;
+ setShowCreateModal(true);
+ }, [readOnly]);
+
+ const handleCreateSubmit = React.useCallback(
+ (data: CreateInvitationInput) => {
+ createInvitationMutation.mutate(data, {
+ onSuccess: () => setShowCreateModal(false),
+ });
+ },
+ [createInvitationMutation],
+ );
+
+ const handleCreateCancel = React.useCallback(() => {
+ setShowCreateModal(false);
+ }, []);
+
+ const handleDetailsClick = React.useCallback(
+ async (invitation: MemberInvitation) => {
+ setSelectedInvitation(invitation);
+ setShowDetailsModal(true);
+ const requestId = ++detailsRequestIdRef.current;
+ try {
+ const response = await fetchInvitationDetails(invitation.id!);
+ if (detailsRequestIdRef.current === requestId) {
+ setSelectedInvitation(response);
+ }
+ } catch {
+ if (detailsRequestIdRef.current === requestId) {
+ showToast({ type: 'error', message: t('invitation.error.fetch_failed') });
+ }
+ }
+ },
+ [fetchInvitationDetails, t],
+ );
+
+ const handleDetailsClose = React.useCallback(() => {
+ setShowDetailsModal(false);
+ setSelectedInvitation(null);
+ }, []);
+
+ const handleRevokeClick = React.useCallback(
+ (invitation: MemberInvitation) => {
+ if (readOnly) return;
+ if (showDetailsModal) {
+ setShowDetailsModal(false);
+ }
+ setSelectedInvitation(invitation);
+ setShowRevokeModal(true);
+ },
+ [readOnly, showDetailsModal],
+ );
+
+ const handleRevokeConfirm = React.useCallback(() => {
+ if (!selectedInvitation) return;
+ revokeInvitationMutation.mutate(selectedInvitation, {
+ onSuccess: () => {
+ setShowRevokeModal(false);
+ setSelectedInvitation(null);
+ },
+ });
+ }, [selectedInvitation, revokeInvitationMutation]);
+
+ const handleRevokeCancel = React.useCallback(() => {
+ setShowRevokeModal(false);
+ }, []);
+
+ const handleRevokeResendClick = React.useCallback(
+ (invitation: MemberInvitation) => {
+ if (readOnly) return;
+ if (showDetailsModal) {
+ setShowDetailsModal(false);
+ }
+ setSelectedInvitation(invitation);
+ setShowRevokeResendModal(true);
+ },
+ [readOnly, showDetailsModal],
+ );
+
+ const handleRevokeResendConfirm = React.useCallback(() => {
+ if (!selectedInvitation) return;
+ resendInvitationMutation.mutate(selectedInvitation, {
+ onSuccess: () => {
+ setShowRevokeResendModal(false);
+ setSelectedInvitation(null);
+ },
+ });
+ }, [selectedInvitation, resendInvitationMutation]);
+
+ const handleRevokeResendCancel = React.useCallback(() => {
+ setShowRevokeResendModal(false);
+ }, []);
+
+ const handleCopyUrl = React.useCallback(
+ async (invitation: MemberInvitation) => {
+ if (!invitation.invitation_url) return;
+ try {
+ await navigator.clipboard.writeText(invitation.invitation_url);
+ showToast({ type: 'success', message: t('invitation.success.url_copied') });
+ } catch {
+ showToast({ type: 'error', message: t('invitation.error.copy_url_failed') });
+ }
+ },
+ [t],
+ );
+
+ const handleNextPage = React.useCallback(() => {
+ if (invitationNextToken) {
+ invitationGoToNextPage(invitationNextToken);
+ }
+ }, [invitationNextToken, invitationGoToNextPage]);
+
+ const handlePreviousPage = React.useCallback(() => {
+ invitationGoToPreviousPage();
+ }, [invitationGoToPreviousPage]);
+
+ const handlePageSizeChange = React.useCallback(
+ (pageSize: number) => {
+ invitationChangePageSize(pageSize);
+ },
+ [invitationChangePageSize],
+ );
+
+ const handleSortChange = React.useCallback(
+ (sortConfig: InvitationSortConfig) => {
+ invitationChangeSortConfig(sortConfig);
+ },
+ [invitationChangeSortConfig],
+ );
+
+ const handleRoleFilterChange = React.useCallback(
+ (roleId: string | undefined) => {
+ invitationChangeFilters((prev) => ({ ...prev, roleId }));
+ },
+ [invitationChangeFilters],
+ );
+
+ return {
+ activeTab,
+ isLoading: invitationsQuery.isLoading || invitationsQuery.isFetching,
+ availableRoles,
+ availableProviders,
+
+ invitations: currentInvitations,
+ isFetchingInvitations: invitationsQuery.isLoading || invitationsQuery.isFetching,
+ isCreatingInvitation: createInvitationMutation.isPending,
+ isRevokingInvitation: revokeInvitationMutation.isPending,
+ isResendingInvitation: resendInvitationMutation.isPending,
+ invitationPagination: {
+ pageSize: invitationPageSize,
+ currentPage: invitationCurrentPage,
+ totalItems: invitationsTotalItems,
+ hasNextPage: !!invitationNextToken,
+ hasPreviousPage: invitationHasPreviousPage,
+ },
+ invitationFilters,
+ invitationSortConfig,
+ showCreateModal,
+ showDetailsModal,
+ showRevokeModal,
+ showRevokeResendModal,
+ selectedInvitation,
+
+ setActiveTab,
+ handleCreateClick,
+ handleCreateSubmit,
+ handleCreateCancel,
+ handleDetailsClick,
+ handleDetailsClose,
+ handleRevokeClick,
+ handleRevokeConfirm,
+ handleRevokeCancel,
+ handleRevokeResendClick,
+ handleRevokeResendConfirm,
+ handleRevokeResendCancel,
+ handleCopyUrl,
+ handleNextPage,
+ handlePreviousPage,
+ handlePageSizeChange,
+ handleSortChange,
+ handleRoleFilterChange,
+ };
+}
From ecd20d81fbf7782064aa2da6ca3013eedcec8dbb Mon Sep 17 00:00:00 2001
From: rax7389
Date: Wed, 15 Apr 2026 17:41:39 +0530
Subject: [PATCH 2/5] chore: refactored code
---
.../src/app/member-management/page.tsx | 4 +-
.../organization-member-management.tsx | 57 ++++----
.../services/use-member-management-service.ts | 2 +-
.../src/hooks/my-organization/use-config.ts | 2 +-
.../use-organization-member-management.ts | 135 +++++-------------
5 files changed, 73 insertions(+), 127 deletions(-)
diff --git a/examples/next-rwa/src/app/member-management/page.tsx b/examples/next-rwa/src/app/member-management/page.tsx
index 7dcc22ec3..9e354e616 100644
--- a/examples/next-rwa/src/app/member-management/page.tsx
+++ b/examples/next-rwa/src/app/member-management/page.tsx
@@ -1,6 +1,6 @@
'use client';
-// import { OrganizationMemberManagement } from '@auth0/universal-components-react/rwa';
+import { OrganizationMemberManagement } from '@auth0/universal-components-react/rwa';
export default function MemberManagementPage() {
return (
@@ -15,7 +15,7 @@ export default function MemberManagementPage() {
{' '}
on how to add Member Management component.
- {/* */}
+
);
}
diff --git a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
index b5338b493..c5bf919cd 100644
--- a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
+++ b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
@@ -57,23 +57,13 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
invitationSortConfig,
availableRoles,
availableProviders,
- showCreateModal,
- showDetailsModal,
- showRevokeModal,
- showRevokeResendModal,
- selectedInvitation,
+ modalState,
setActiveTab,
- handleCreateClick,
+ openModal,
+ closeModal,
handleCreateSubmit,
- handleCreateCancel,
- handleDetailsClick,
- handleDetailsClose,
- handleRevokeClick,
handleRevokeConfirm,
- handleRevokeCancel,
- handleRevokeResendClick,
handleRevokeResendConfirm,
- handleRevokeResendCancel,
handleCopyUrl,
handleSortChange,
handleNextPage,
@@ -82,6 +72,13 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
handleRoleFilterChange,
} = props;
+ const selectedInvitation =
+ modalState.type === 'details' ||
+ modalState.type === 'revoke' ||
+ modalState.type === 'revokeResend'
+ ? modalState.invitation
+ : null;
+
const { isDarkMode } = useTheme();
const { t } = useTranslator('member_management', customMessages as Record);
@@ -104,7 +101,7 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
{
type: 'button',
label: t('invite_button'),
- onClick: handleCreateClick,
+ onClick: () => openModal({ type: 'create' }),
icon: Plus,
disabled: readOnly,
},
@@ -141,10 +138,16 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
readOnly={readOnly}
sortConfig={invitationSortConfig}
onSortChange={handleSortChange}
- onView={handleDetailsClick}
+ onView={(invitation) => openModal({ type: 'details', invitation })}
onCopyUrl={handleCopyUrl}
- onRevokeAndResend={readOnly ? undefined : handleRevokeResendClick}
- onRevoke={readOnly ? undefined : handleRevokeClick}
+ onRevokeAndResend={
+ readOnly
+ ? undefined
+ : (invitation) => openModal({ type: 'revokeResend', invitation })
+ }
+ onRevoke={
+ readOnly ? undefined : (invitation) => openModal({ type: 'revoke', invitation })
+ }
onNextPage={handleNextPage}
onPreviousPage={handlePreviousPage}
onPageSizeChange={handlePageSizeChange}
@@ -155,49 +158,49 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
invitation && handleRevokeClick(invitation)}
- onResend={(invitation) => invitation && handleRevokeResendClick(invitation)}
+ onRevoke={(invitation) => invitation && openModal({ type: 'revoke', invitation })}
+ onResend={(invitation) => invitation && openModal({ type: 'revokeResend', invitation })}
className={currentStyles.classes?.['OrganizationInvitationTab-detailsModal']}
/>
handleRevokeConfirm()}
className={currentStyles.classes?.['OrganizationInvitationTab-revokeModal']}
/>
handleRevokeResendConfirm()}
className={currentStyles.classes?.['OrganizationInvitationTab-revokeResendModal']}
/>
diff --git a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
index 3adbec2a8..f3a31f925 100644
--- a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
+++ b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
@@ -116,7 +116,7 @@ export function useMemberManagementService(
const response = await coreClient!
.getMyOrganizationApiClient()
.organization.invitations.create({
- invitees: [{ email: data.invitee.email, roles: data.roles }],
+ invitees: data.invitees,
inviter: data.inviter,
ttl_sec: data.ttl_sec,
});
diff --git a/packages/react/src/hooks/my-organization/use-config.ts b/packages/react/src/hooks/my-organization/use-config.ts
index 98cc264a0..bf0488256 100644
--- a/packages/react/src/hooks/my-organization/use-config.ts
+++ b/packages/react/src/hooks/my-organization/use-config.ts
@@ -63,6 +63,6 @@ export function useConfig(): UseConfigResult {
filteredStrategies,
shouldAllowDeletion,
isConfigValid,
- allowedRoles: [],
+ allowedRoles,
};
}
diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
index 1af33e6da..e7c5b232e 100644
--- a/packages/react/src/hooks/my-organization/use-organization-member-management.ts
+++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
@@ -20,6 +20,7 @@ import type {
} from '@/types/my-organization/member-management/organization-invitation-table-types';
import type {
ActiveTab,
+ MemberManagementModalState,
UseOrganizationMemberManagementOptions,
UseOrganizationMemberManagementResult,
} from '@/types/my-organization/member-management/organization-member-management-types';
@@ -63,11 +64,7 @@ export function useOrganizationMemberManagement(
changeFilters: invitationChangeFilters,
} = useCheckpointPagination();
- const [showCreateModal, setShowCreateModal] = React.useState(false);
- const [showDetailsModal, setShowDetailsModal] = React.useState(false);
- const [showRevokeModal, setShowRevokeModal] = React.useState(false);
- const [showRevokeResendModal, setShowRevokeResendModal] = React.useState(false);
- const [selectedInvitation, setSelectedInvitation] = React.useState(null);
+ const [modalState, setModalState] = React.useState({ type: null });
const detailsRequestIdRef = React.useRef(0);
const {
@@ -96,99 +93,55 @@ export function useOrganizationMemberManagement(
const invitationNextToken = invitationsQuery.data?.next ?? null;
const invitationsTotalItems = invitationsQuery.data?.total;
- const handleCreateClick = React.useCallback(() => {
- if (readOnly) return;
- setShowCreateModal(true);
- }, [readOnly]);
-
- const handleCreateSubmit = React.useCallback(
- (data: CreateInvitationInput) => {
- createInvitationMutation.mutate(data, {
- onSuccess: () => setShowCreateModal(false),
- });
- },
- [createInvitationMutation],
- );
-
- const handleCreateCancel = React.useCallback(() => {
- setShowCreateModal(false);
- }, []);
-
- const handleDetailsClick = React.useCallback(
- async (invitation: MemberInvitation) => {
- setSelectedInvitation(invitation);
- setShowDetailsModal(true);
- const requestId = ++detailsRequestIdRef.current;
- try {
- const response = await fetchInvitationDetails(invitation.id!);
- if (detailsRequestIdRef.current === requestId) {
- setSelectedInvitation(response);
- }
- } catch {
- if (detailsRequestIdRef.current === requestId) {
- showToast({ type: 'error', message: t('invitation.error.fetch_failed') });
+ const openModal = React.useCallback(
+ async (state: MemberManagementModalState) => {
+ if (state.type === 'create' && readOnly) return;
+ if ((state.type === 'revoke' || state.type === 'revokeResend') && readOnly) return;
+ setModalState(state);
+
+ if (state.type === 'details') {
+ const requestId = ++detailsRequestIdRef.current;
+ try {
+ const response = await fetchInvitationDetails(state.invitation.id!);
+ if (detailsRequestIdRef.current === requestId) {
+ setModalState({ type: 'details', invitation: response });
+ }
+ } catch {
+ if (detailsRequestIdRef.current === requestId) {
+ showToast({ type: 'error', message: t('invitation.error.fetch_failed') });
+ }
}
}
},
- [fetchInvitationDetails, t],
+ [readOnly, fetchInvitationDetails, t],
);
- const handleDetailsClose = React.useCallback(() => {
- setShowDetailsModal(false);
- setSelectedInvitation(null);
+ const closeModal = React.useCallback(() => {
+ setModalState({ type: null });
}, []);
- const handleRevokeClick = React.useCallback(
- (invitation: MemberInvitation) => {
- if (readOnly) return;
- if (showDetailsModal) {
- setShowDetailsModal(false);
- }
- setSelectedInvitation(invitation);
- setShowRevokeModal(true);
+ const handleCreateSubmit = React.useCallback(
+ (data: CreateInvitationInput) => {
+ createInvitationMutation.mutate(data, {
+ onSuccess: () => closeModal(),
+ });
},
- [readOnly, showDetailsModal],
+ [createInvitationMutation, closeModal],
);
const handleRevokeConfirm = React.useCallback(() => {
- if (!selectedInvitation) return;
- revokeInvitationMutation.mutate(selectedInvitation, {
- onSuccess: () => {
- setShowRevokeModal(false);
- setSelectedInvitation(null);
- },
+ if (modalState.type !== 'revoke') return;
+ revokeInvitationMutation.mutate(modalState.invitation, {
+ onSuccess: () => closeModal(),
});
- }, [selectedInvitation, revokeInvitationMutation]);
-
- const handleRevokeCancel = React.useCallback(() => {
- setShowRevokeModal(false);
- }, []);
-
- const handleRevokeResendClick = React.useCallback(
- (invitation: MemberInvitation) => {
- if (readOnly) return;
- if (showDetailsModal) {
- setShowDetailsModal(false);
- }
- setSelectedInvitation(invitation);
- setShowRevokeResendModal(true);
- },
- [readOnly, showDetailsModal],
- );
+ }, [modalState, revokeInvitationMutation, closeModal]);
const handleRevokeResendConfirm = React.useCallback(() => {
- if (!selectedInvitation) return;
- resendInvitationMutation.mutate(selectedInvitation, {
- onSuccess: () => {
- setShowRevokeResendModal(false);
- setSelectedInvitation(null);
- },
+ if (modalState.type !== 'revokeResend') return;
+ resendInvitationMutation.mutate(modalState.invitation, {
+ onSuccess: () => closeModal(),
});
- }, [selectedInvitation, resendInvitationMutation]);
-
- const handleRevokeResendCancel = React.useCallback(() => {
- setShowRevokeResendModal(false);
- }, []);
+ }, [modalState, resendInvitationMutation, closeModal]);
const handleCopyUrl = React.useCallback(
async (invitation: MemberInvitation) => {
@@ -254,24 +207,14 @@ export function useOrganizationMemberManagement(
},
invitationFilters,
invitationSortConfig,
- showCreateModal,
- showDetailsModal,
- showRevokeModal,
- showRevokeResendModal,
- selectedInvitation,
+ modalState,
setActiveTab,
- handleCreateClick,
+ openModal,
+ closeModal,
handleCreateSubmit,
- handleCreateCancel,
- handleDetailsClick,
- handleDetailsClose,
- handleRevokeClick,
handleRevokeConfirm,
- handleRevokeCancel,
- handleRevokeResendClick,
handleRevokeResendConfirm,
- handleRevokeResendCancel,
handleCopyUrl,
handleNextPage,
handlePreviousPage,
From f91718560b38396fc8b1a87d7599fdcbde1736de Mon Sep 17 00:00:00 2001
From: rax7389
Date: Tue, 28 Apr 2026 21:58:18 +0530
Subject: [PATCH 3/5] chore: addressed review comments
---
.../next-rwa/src/app/member-management/page.tsx | 2 +-
.../organization-member-management.tsx | 2 +-
.../services/use-member-management-service.ts | 14 ++++++++------
.../use-organization-member-management.ts | 1 -
.../organization-member-management-types.ts | 1 -
5 files changed, 10 insertions(+), 10 deletions(-)
diff --git a/examples/next-rwa/src/app/member-management/page.tsx b/examples/next-rwa/src/app/member-management/page.tsx
index 9e354e616..ab39b4930 100644
--- a/examples/next-rwa/src/app/member-management/page.tsx
+++ b/examples/next-rwa/src/app/member-management/page.tsx
@@ -1,6 +1,6 @@
'use client';
-import { OrganizationMemberManagement } from '@auth0/universal-components-react/rwa';
+import { OrganizationMemberManagement } from '@auth0/universal-components-react';
export default function MemberManagementPage() {
return (
diff --git a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
index c5bf919cd..8dd68b07e 100644
--- a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
+++ b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
@@ -234,7 +234,7 @@ export function OrganizationMemberManagement(props: OrganizationMemberManagement
});
return (
-
+
);
+ const handleError = useErrorHandler();
const queryClient = useQueryClient();
const providersQuery = useQuery({
@@ -127,8 +129,8 @@ export function useMemberManagementService(
showToast({ type: 'success', message: t('invitation.create.success') });
queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
},
- onError: () => {
- showToast({ type: 'error', message: t('invitation.error.create_failed') });
+ onError: (error) => {
+ handleError(error, { fallbackMessage: t('invitation.error.create_failed') });
},
});
@@ -147,8 +149,8 @@ export function useMemberManagementService(
showToast({ type: 'success', message: t('invitation.revoke.success') });
queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
},
- onError: () => {
- showToast({ type: 'error', message: t('invitation.error.revoke_failed') });
+ onError: (error) => {
+ handleError(error, { fallbackMessage: t('invitation.error.revoke_failed') });
},
});
@@ -177,8 +179,8 @@ export function useMemberManagementService(
showToast({ type: 'success', message: t('invitation.success.invitation_resent') });
queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
},
- onError: () => {
- showToast({ type: 'error', message: t('invitation.error.resend_failed') });
+ onError: (error) => {
+ handleError(error, { fallbackMessage: t('invitation.error.resend_failed') });
queryClient.invalidateQueries({ queryKey: memberManagementQueryKeys.invitations() });
},
});
diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
index e7c5b232e..2f9ad471f 100644
--- a/packages/react/src/hooks/my-organization/use-organization-member-management.ts
+++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
@@ -189,7 +189,6 @@ export function useOrganizationMemberManagement(
return {
activeTab,
- isLoading: invitationsQuery.isLoading || invitationsQuery.isFetching,
availableRoles,
availableProviders,
diff --git a/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts b/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
index aa12341c7..f814dd840 100644
--- a/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
+++ b/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
@@ -81,7 +81,6 @@ export type MemberManagementModalState =
export interface UseOrganizationMemberManagementResult {
activeTab: ActiveTab;
- isLoading: boolean;
availableRoles: RoleOption[];
availableProviders: IdentityProviderOption[];
From bd849989659d88ba0d564fd98af9884ba501ab86 Mon Sep 17 00:00:00 2001
From: rax7389
Date: Tue, 28 Apr 2026 23:20:58 +0530
Subject: [PATCH 4/5] chore: addressed review comments
---
packages/core/src/services/my-organization/index.ts | 1 +
.../member-management/member-management-constants.ts | 10 ++++++++++
.../shared/services/use-member-management-service.ts | 6 +-----
.../use-organization-member-management.ts | 2 --
4 files changed, 12 insertions(+), 7 deletions(-)
create mode 100644 packages/core/src/services/my-organization/member-management/member-management-constants.ts
diff --git a/packages/core/src/services/my-organization/index.ts b/packages/core/src/services/my-organization/index.ts
index 5e1816d52..bd65eeae2 100644
--- a/packages/core/src/services/my-organization/index.ts
+++ b/packages/core/src/services/my-organization/index.ts
@@ -8,4 +8,5 @@ export * from './organization-management';
export * from './idp-management';
export * from './domain-management';
export * from './member-management/member-management-types';
+export * from './member-management/member-management-constants';
export * from './config';
diff --git a/packages/core/src/services/my-organization/member-management/member-management-constants.ts b/packages/core/src/services/my-organization/member-management/member-management-constants.ts
new file mode 100644
index 000000000..2290e2308
--- /dev/null
+++ b/packages/core/src/services/my-organization/member-management/member-management-constants.ts
@@ -0,0 +1,10 @@
+/**
+ * Member management constants.
+ * @module member-management-constants
+ * @internal
+ */
+
+export const memberManagementQueryKeys = {
+ all: ['member-management'] as const,
+ invitations: () => [...memberManagementQueryKeys.all, 'invitations'] as const,
+};
diff --git a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
index f17409b87..78634ec34 100644
--- a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
+++ b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
@@ -7,6 +7,7 @@
import {
type MemberInvitation,
type ListIdentityProvidersResponseContent,
+ memberManagementQueryKeys,
} from '@auth0/universal-components-core';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import * as React from 'react';
@@ -24,11 +25,6 @@ import type {
MemberManagementServiceResult,
} from '@/types/my-organization/member-management/organization-member-management-types';
-export const memberManagementQueryKeys = {
- all: ['member-management'] as const,
- invitations: () => [...memberManagementQueryKeys.all, 'invitations'] as const,
-};
-
const INVITATION_SORT_FIELD_MAP: Record = {
created_at: 'created_at',
};
diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
index 2f9ad471f..5138589ff 100644
--- a/packages/react/src/hooks/my-organization/use-organization-member-management.ts
+++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
@@ -25,8 +25,6 @@ import type {
UseOrganizationMemberManagementResult,
} from '@/types/my-organization/member-management/organization-member-management-types';
-export { memberManagementQueryKeys } from '@/hooks/my-organization/shared/services/use-member-management-service';
-
/**
* Hook for organization member management.
* @param options - Hook configuration options.
From d712faa43b3c9fe8737a7399c5c048faaca2e7c2 Mon Sep 17 00:00:00 2001
From: rax7389
Date: Wed, 29 Apr 2026 21:49:08 +0530
Subject: [PATCH 5/5] chore: review comments addressed
---
.../organization-member-management.tsx | 19 ++++---------------
.../services/use-member-management-service.ts | 2 +-
.../use-organization-member-management.ts | 2 +-
.../organization-member-management-types.ts | 15 +++++++++++++--
4 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
index 8dd68b07e..09bd4b032 100644
--- a/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
+++ b/packages/react/src/components/auth0/my-organization/organization-member-management.tsx
@@ -21,20 +21,9 @@ import { useTheme } from '@/hooks/shared/use-theme';
import { useTranslator } from '@/hooks/shared/use-translator';
import type {
OrganizationMemberManagementProps,
- UseOrganizationMemberManagementResult,
+ OrganizationMemberManagementViewProps,
} from '@/types/my-organization/member-management/organization-member-management-types';
-/**
- * Props for the OrganizationMemberManagementView component.
- */
-export interface OrganizationMemberManagementViewProps
- extends UseOrganizationMemberManagementResult {
- styling: OrganizationMemberManagementProps['styling'];
- customMessages: OrganizationMemberManagementProps['customMessages'];
- hideHeader: boolean;
- readOnly: boolean;
-}
-
/**
* View component for organization member management.
* @param props - The component props.
@@ -80,7 +69,7 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
: null;
const { isDarkMode } = useTheme();
- const { t } = useTranslator('member_management', customMessages as Record);
+ const { t } = useTranslator('member_management', customMessages);
const currentStyles = React.useMemo(
() => getComponentStyles(styling, isDarkMode),
@@ -190,7 +179,7 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
isLoading={isRevokingInvitation}
customMessages={customMessages?.invitation}
onClose={closeModal}
- onConfirm={() => handleRevokeConfirm()}
+ onConfirm={handleRevokeConfirm}
className={currentStyles.classes?.['OrganizationInvitationTab-revokeModal']}
/>
@@ -201,7 +190,7 @@ export function OrganizationMemberManagementView(props: OrganizationMemberManage
isRevokeAndResend
customMessages={customMessages?.invitation}
onClose={closeModal}
- onConfirm={() => handleRevokeResendConfirm()}
+ onConfirm={handleRevokeResendConfirm}
className={currentStyles.classes?.['OrganizationInvitationTab-revokeResendModal']}
/>
diff --git a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
index 78634ec34..a8419c953 100644
--- a/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
+++ b/packages/react/src/hooks/my-organization/shared/services/use-member-management-service.ts
@@ -62,7 +62,7 @@ export function useMemberManagementService(
const isInvitationsTabActive = activeTab === 'invitations';
const { coreClient } = useCoreClient();
- const { t } = useTranslator('member_management', customMessages as Record);
+ const { t } = useTranslator('member_management', customMessages);
const handleError = useErrorHandler();
const queryClient = useQueryClient();
diff --git a/packages/react/src/hooks/my-organization/use-organization-member-management.ts b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
index 5138589ff..01b62b429 100644
--- a/packages/react/src/hooks/my-organization/use-organization-member-management.ts
+++ b/packages/react/src/hooks/my-organization/use-organization-member-management.ts
@@ -41,7 +41,7 @@ export function useOrganizationMemberManagement(
resendInvitationAction,
} = options;
- const { t } = useTranslator('member_management', customMessages as Record);
+ const { t } = useTranslator('member_management', customMessages);
const [activeTab, setActiveTab] = React.useState('members');
diff --git a/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts b/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
index f814dd840..7acd0589b 100644
--- a/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
+++ b/packages/react/src/types/my-organization/member-management/organization-member-management-types.ts
@@ -31,7 +31,7 @@ export interface TableQueryParams {
}
export interface UseMemberManagementServiceOptions {
- customMessages?: OrganizationMemberManagementMessages;
+ customMessages?: Partial;
activeTab: ActiveTab;
createInvitationAction?: ComponentAction;
revokeInvitationAction?: ComponentAction;
@@ -61,7 +61,7 @@ export interface MemberManagementServiceResult {
}
export interface UseOrganizationMemberManagementOptions {
- customMessages?: OrganizationMemberManagementMessages;
+ customMessages?: Partial;
readOnly?: boolean;
/** Action hooks for invitation creation (onBefore/onAfter) */
createInvitationAction?: ComponentAction;
@@ -108,6 +108,17 @@ export interface UseOrganizationMemberManagementResult {
handleRoleFilterChange: (roleId: string | undefined) => void;
}
+/**
+ * Props for the OrganizationMemberManagementView component.
+ */
+export interface OrganizationMemberManagementViewProps
+ extends UseOrganizationMemberManagementResult {
+ styling: OrganizationMemberManagementProps['styling'];
+ customMessages: OrganizationMemberManagementProps['customMessages'];
+ hideHeader: boolean;
+ readOnly: boolean;
+}
+
/** CSS classes for OrganizationMemberManagement. */
export interface OrganizationMemberManagementClasses extends OrganizationInvitationTabClasses {
'OrganizationMemberManagement-root'?: string;