diff --git a/packages/react/src/components/auth0/my-organization/__tests__/domain-table.test.tsx b/packages/react/src/components/auth0/my-organization/__tests__/domain-table.test.tsx
index 1908ddcb2..17417e5f8 100644
--- a/packages/react/src/components/auth0/my-organization/__tests__/domain-table.test.tsx
+++ b/packages/react/src/components/auth0/my-organization/__tests__/domain-table.test.tsx
@@ -13,8 +13,7 @@ import {
createMockCreateAction,
createMockVerifyAction,
createMockDeleteAction,
- createMockLogic,
- createMockApi,
+ createMockDomainTableReturn,
} from '@/tests/utils/__mocks__/my-organization/domain-management/domain.mocks';
import { renderWithProviders } from '@/tests/utils/test-provider';
import { mockCore, mockToast } from '@/tests/utils/test-setup';
@@ -681,27 +680,32 @@ describe('DomainTable', () => {
});
describe('DomainTableView', () => {
- // Provide all required handlers and properties for UseDomainTableResult & DomainTableProps
- const logic = createMockLogic();
- const handlers = createMockApi();
+ const mockDomainTable = createMockDomainTableReturn();
+ const defaultViewProps = {
+ domainTable: mockDomainTable,
+ schema: undefined,
+ styling: { variables: { common: {}, light: {}, dark: {} }, classes: {} },
+ hideHeader: false,
+ readOnly: false,
+ customMessages: {},
+ createAction: undefined,
+ onOpenProvider: undefined,
+ onCreateProvider: undefined,
+ };
it('renders the table and header', () => {
- renderWithProviders();
+ renderWithProviders();
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.getByText(/header.title/i)).toBeInTheDocument();
});
it('does not render header if hideHeader is true', () => {
- renderWithProviders(
- ,
- );
+ renderWithProviders();
expect(screen.queryByText(/header.title/i)).not.toBeInTheDocument();
});
it('disables create button if readOnly is true', () => {
- renderWithProviders(
- ,
- );
+ renderWithProviders();
expect(screen.getByRole('button', { name: /create/i })).toBeDisabled();
});
});
diff --git a/packages/react/src/components/auth0/my-organization/domain-table.tsx b/packages/react/src/components/auth0/my-organization/domain-table.tsx
index 10d01c95b..97d3a6cb8 100644
--- a/packages/react/src/components/auth0/my-organization/domain-table.tsx
+++ b/packages/react/src/components/auth0/my-organization/domain-table.tsx
@@ -15,7 +15,6 @@ import { Header } from '@/components/auth0/shared/header';
import { StyledScope } from '@/components/auth0/shared/styled-scope';
import { Badge } from '@/components/ui/badge';
import { useDomainTable } from '@/hooks/my-organization/use-domain-table';
-import { useDomainTableLogic } from '@/hooks/my-organization/use-domain-table-logic';
import { useTheme } from '@/hooks/shared/use-theme';
import { useTranslator } from '@/hooks/shared/use-translator';
import { getStatusBadgeVariant } from '@/lib/utils/my-organization/domain-management/domain-management-utils';
@@ -46,9 +45,7 @@ function DomainTable(props: DomainTableProps) {
onCreateProvider,
} = props;
- const { t } = useTranslator('domain_management', customMessages);
-
- const domainTableState = useDomainTable({
+ const domainTable = useDomainTable({
createAction,
verifyAction,
deleteAction,
@@ -57,46 +54,42 @@ function DomainTable(props: DomainTableProps) {
customMessages,
});
- const domainTableHandlers = useDomainTableLogic({
- t,
- onCreateDomain: domainTableState.onCreateDomain,
- onVerifyDomain: domainTableState.onVerifyDomain,
- onDeleteDomain: domainTableState.onDeleteDomain,
- onAssociateToProvider: domainTableState.onAssociateToProvider,
- onDeleteFromProvider: domainTableState.onDeleteFromProvider,
- fetchProviders: domainTableState.fetchProviders,
- fetchDomains: domainTableState.fetchDomains,
- });
-
- const domainTableLogic = {
- ...domainTableState,
- schema,
- styling,
- hideHeader,
- readOnly,
- onOpenProvider,
- onCreateProvider,
- };
-
return (
-
-
+
+
);
}
/**
* DomainTableView — Presentational component.
- * @param props - View props with logic and handlers
+ * @param props - View props
* @returns Domain table view element
* @internal
*/
function DomainTableView({
- logic,
- handlers,
-}: DomainTableViewProps & { handlers: ReturnType }) {
+ domainTable,
+ schema,
+ styling,
+ hideHeader,
+ readOnly = false,
+ customMessages,
+ createAction,
+ onOpenProvider,
+ onCreateProvider,
+}: DomainTableViewProps) {
const { isDarkMode } = useTheme();
- const { t } = useTranslator('domain_management', logic.customMessages);
+ const { t } = useTranslator('domain_management', customMessages);
const {
domains,
@@ -106,17 +99,6 @@ function DomainTableView({
isFetching,
isLoadingProviders,
isDeleting,
- schema,
- styling,
- hideHeader,
- readOnly = false,
- customMessages,
- createAction,
- onOpenProvider,
- onCreateProvider,
- } = logic;
-
- const {
showCreateModal,
showConfigureModal,
showVerifyModal,
@@ -135,7 +117,7 @@ function DomainTableView({
handleConfigureClick,
handleVerifyClick,
handleDeleteClick,
- } = handlers;
+ } = domainTable;
const currentStyles = React.useMemo(
() => getComponentStyles(styling, isDarkMode),
@@ -172,7 +154,7 @@ function DomainTableView({
,
-): UseDomainTableLogicOptions => ({
- t: createMockI18nService().translator('my-organization'),
- onCreateDomain: vi.fn(),
- onVerifyDomain: vi.fn(),
- onDeleteDomain: vi.fn(),
- onAssociateToProvider: vi.fn(),
- onDeleteFromProvider: vi.fn(),
- fetchProviders: vi.fn(),
- fetchDomains: vi.fn(),
- ...overrides,
-});
-
-// ===== Tests =====
-
-describe('useDomainTableLogic', () => {
- let mockCoreClient: ReturnType;
- let mockHandleError: ReturnType;
- let mockOptions: UseDomainTableLogicOptions;
-
- beforeEach(() => {
- vi.clearAllMocks();
-
- mockCoreClient = initMockCoreClient();
- mockHandleError = vi.fn();
- mockOptions = createMockOptions();
-
- vi.spyOn(useCoreClientModule, 'useCoreClient').mockReturnValue({
- coreClient: mockCoreClient,
- });
-
- vi.spyOn(useErrorHandlerModule, 'useErrorHandler').mockReturnValue(mockHandleError);
- });
-
- describe('Initial State', () => {
- it('should initialize with correct default state', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- expect(result.current.showCreateModal).toBe(false);
- expect(result.current.showConfigureModal).toBe(false);
- expect(result.current.showVerifyModal).toBe(false);
- expect(result.current.showDeleteModal).toBe(false);
- expect(result.current.verifyError).toBeUndefined();
- expect(result.current.selectedDomain).toBeNull();
- });
-
- it('should call fetchDomains on mount when coreClient is available', async () => {
- renderHook(() => useDomainTableLogic(mockOptions));
-
- await waitFor(() => {
- expect(mockOptions.fetchDomains).toHaveBeenCalledTimes(1);
- });
- });
-
- it('should handle fetchDomains error on initialization', async () => {
- const error = new Error('Fetch domains failed');
- const mockFetchDomains = vi.fn().mockImplementation(() => {
- throw error;
- });
- const options = createMockOptions({ fetchDomains: mockFetchDomains });
-
- renderHook(() => useDomainTableLogic(options));
-
- await waitFor(() => {
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.fetch_domains_error',
- });
- });
- });
- });
-
- describe('Modal State Management', () => {
- it('should update create modal state', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.setShowCreateModal(true);
- });
-
- expect(result.current.showCreateModal).toBe(true);
- });
-
- it('should update configure modal state', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.setShowConfigureModal(true);
- });
-
- expect(result.current.showConfigureModal).toBe(true);
- });
-
- it('should update verify modal state', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.setShowVerifyModal(true);
- });
-
- expect(result.current.showVerifyModal).toBe(true);
- });
-
- it('should update delete modal state', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.setShowDeleteModal(true);
- });
-
- expect(result.current.showDeleteModal).toBe(true);
- });
- });
-
- describe('handleCreate', () => {
- it('should create domain successfully and show verify modal', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockOnCreateDomain = vi.fn().mockResolvedValue(mockDomain);
- const options = createMockOptions({ onCreateDomain: mockOnCreateDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleCreate('test.com');
- });
-
- expect(mockOnCreateDomain).toHaveBeenCalledWith({ domain: 'test.com' });
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_create.success',
- });
- expect(result.current.selectedDomain).toEqual(mockDomain);
- expect(result.current.showCreateModal).toBe(false);
- expect(result.current.showVerifyModal).toBe(true);
- });
-
- it('should handle create domain error', async () => {
- const error = new Error('Create failed');
- const mockOnCreateDomain = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onCreateDomain: mockOnCreateDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleCreate('test.com');
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_create.error',
- });
- });
- });
-
- describe('handleVerify', () => {
- it('should verify domain successfully and close verify modal', async () => {
- const mockDomain = createMockDomain();
- const mockOnVerifyDomain = vi.fn().mockResolvedValue(true);
- const options = createMockOptions({ onVerifyDomain: mockOnVerifyDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerify(mockDomain);
- });
-
- expect(mockOnVerifyDomain).toHaveBeenCalledWith(mockDomain);
- expect(result.current.showVerifyModal).toBe(false);
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_verify.success',
- });
- });
-
- it('should handle verification failure and set verify error', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockOnVerifyDomain = vi.fn().mockResolvedValue(false);
- const options = createMockOptions({ onVerifyDomain: mockOnVerifyDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerify(mockDomain);
- });
-
- expect(result.current.verifyError).toBe('domain_verify.modal.errors.verification_failed');
- });
-
- it('should handle verify domain error', async () => {
- const mockDomain = createMockDomain();
- const error = new Error('Verify failed');
- const mockOnVerifyDomain = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onVerifyDomain: mockOnVerifyDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerify(mockDomain);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_verify.error',
- });
- });
- });
-
- describe('handleDelete', () => {
- it('should delete domain successfully', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockOnDeleteDomain = vi.fn().mockResolvedValue(undefined);
- const options = createMockOptions({ onDeleteDomain: mockOnDeleteDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleDelete(mockDomain);
- });
-
- expect(mockOnDeleteDomain).toHaveBeenCalledWith(mockDomain);
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_delete.success',
- });
- expect(result.current.showDeleteModal).toBe(false);
- expect(result.current.showVerifyModal).toBe(false);
- });
-
- it('should handle delete domain error', async () => {
- const mockDomain = createMockDomain();
- const error = new Error('Delete failed');
- const mockOnDeleteDomain = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onDeleteDomain: mockOnDeleteDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleDelete(mockDomain);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_delete.error',
- });
- });
- });
-
- describe('handleToggleSwitch', () => {
- it('should associate domain to provider when checked is true', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockProvider = createMockIdentityProvider({ name: 'TestIDP' });
- const mockOnAssociateToProvider = vi.fn().mockResolvedValue(undefined);
- const options = createMockOptions({ onAssociateToProvider: mockOnAssociateToProvider });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleToggleSwitch(mockDomain, mockProvider, true);
- });
-
- expect(mockOnAssociateToProvider).toHaveBeenCalledWith(mockDomain, mockProvider);
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_associate_provider.success',
- });
- });
-
- it('should delete domain from provider when checked is false', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockProvider = createMockIdentityProvider({ name: 'TestIDP' });
- const mockOnDeleteFromProvider = vi.fn().mockResolvedValue(undefined);
- const options = createMockOptions({ onDeleteFromProvider: mockOnDeleteFromProvider });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleToggleSwitch(mockDomain, mockProvider, false);
- });
-
- expect(mockOnDeleteFromProvider).toHaveBeenCalledWith(mockDomain, mockProvider);
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_delete_provider.success',
- });
- });
-
- it('should handle associate to provider error', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const error = new Error('Associate failed');
- const mockOnAssociateToProvider = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onAssociateToProvider: mockOnAssociateToProvider });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleToggleSwitch(mockDomain, mockProvider, true);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_associate_provider.error',
- });
- });
-
- it('should handle delete from provider error', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const error = new Error('Delete from provider failed');
- const mockOnDeleteFromProvider = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onDeleteFromProvider: mockOnDeleteFromProvider });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleToggleSwitch(mockDomain, mockProvider, false);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_delete_provider.error',
- });
- });
- });
-
- describe('handleCloseVerifyModal', () => {
- it('should close verify modal and clear verify error', async () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- // Set initial state
- act(() => {
- result.current.setShowVerifyModal(true);
- });
-
- // Simulate verify error
- await act(async () => {
- await result.current.handleVerify(createMockDomain());
- });
-
- // Close modal
- act(() => {
- result.current.handleCloseVerifyModal();
- });
-
- expect(result.current.showVerifyModal).toBe(false);
- expect(result.current.verifyError).toBeUndefined();
- });
- });
-
- describe('handleCreateClick', () => {
- it('should show create modal', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.handleCreateClick();
- });
-
- expect(result.current.showCreateModal).toBe(true);
- });
- });
-
- describe('handleConfigureClick', () => {
- it('should show verify modal for unverified domain', async () => {
- const mockDomain = createMockDomain({ status: 'pending' });
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- await act(async () => {
- await result.current.handleConfigureClick(mockDomain);
- });
-
- expect(result.current.selectedDomain).toEqual(mockDomain);
- expect(result.current.showVerifyModal).toBe(true);
- });
-
- it('should fetch providers and show configure modal for verified domain', async () => {
- const mockDomain = createMockDomain({ status: 'verified' });
- const mockFetchProviders = vi.fn().mockResolvedValue(undefined);
- const options = createMockOptions({ fetchProviders: mockFetchProviders });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleConfigureClick(mockDomain);
- });
-
- expect(result.current.selectedDomain).toEqual(mockDomain);
- expect(mockFetchProviders).toHaveBeenCalledWith(mockDomain);
- expect(result.current.showConfigureModal).toBe(true);
- });
-
- it('should handle fetchProviders error for verified domain', async () => {
- const mockDomain = createMockDomain({ status: 'verified' });
- const error = new Error('Fetch providers failed');
- const mockFetchProviders = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ fetchProviders: mockFetchProviders });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleConfigureClick(mockDomain);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.fetch_providers_error',
- });
- });
- });
-
- describe('handleVerifyClick', () => {
- it('should verify domain, fetch providers, and show configure modal on success', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockOnVerifyDomain = vi.fn().mockResolvedValue(true);
- const mockFetchProviders = vi.fn().mockResolvedValue(undefined);
- const options = createMockOptions({
- onVerifyDomain: mockOnVerifyDomain,
- fetchProviders: mockFetchProviders,
- });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerifyClick(mockDomain);
- });
-
- expect(result.current.selectedDomain).toEqual(mockDomain);
- expect(mockOnVerifyDomain).toHaveBeenCalledWith(mockDomain);
- expect(mockFetchProviders).toHaveBeenCalledWith(mockDomain);
- expect(result.current.showConfigureModal).toBe(true);
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'success',
- message: 'domain_table.notifications.domain_verify.success',
- });
- });
-
- it('should show error toast on verification failure', async () => {
- const mockDomain = createMockDomain({ domain: 'test.com' });
- const mockOnVerifyDomain = vi.fn().mockResolvedValue(false);
- const options = createMockOptions({ onVerifyDomain: mockOnVerifyDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerifyClick(mockDomain);
- });
-
- expect(mockedShowToast).toHaveBeenCalledWith({
- type: 'error',
- message: 'domain_table.notifications.domain_verify.verification_failed',
- });
- });
-
- it('should handle verify click error', async () => {
- const mockDomain = createMockDomain();
- const error = new Error('Verify click failed');
- const mockOnVerifyDomain = vi.fn().mockRejectedValue(error);
- const options = createMockOptions({ onVerifyDomain: mockOnVerifyDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleVerifyClick(mockDomain);
- });
-
- expect(mockHandleError).toHaveBeenCalledWith(error, {
- fallbackMessage: 'domain_table.notifications.domain_verify.error',
- });
- });
- });
-
- describe('handleDeleteClick', () => {
- it('should set selected domain and show delete modal', () => {
- const mockDomain = createMockDomain();
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- // Set verify modal to true initially
- act(() => {
- result.current.setShowVerifyModal(true);
- });
-
- act(() => {
- result.current.handleDeleteClick(mockDomain);
- });
-
- expect(result.current.selectedDomain).toEqual(mockDomain);
- expect(result.current.showVerifyModal).toBe(false);
- expect(result.current.showDeleteModal).toBe(true);
- });
- });
-
- describe('Edge Cases and Integration', () => {
- it('should handle multiple modal state changes correctly', () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- act(() => {
- result.current.setShowCreateModal(true);
- result.current.setShowConfigureModal(true);
- result.current.setShowVerifyModal(true);
- result.current.setShowDeleteModal(true);
- });
-
- expect(result.current.showCreateModal).toBe(true);
- expect(result.current.showConfigureModal).toBe(true);
- expect(result.current.showVerifyModal).toBe(true);
- expect(result.current.showDeleteModal).toBe(true);
-
- act(() => {
- result.current.setShowCreateModal(false);
- result.current.setShowConfigureModal(false);
- result.current.setShowVerifyModal(false);
- result.current.setShowDeleteModal(false);
- });
-
- expect(result.current.showCreateModal).toBe(false);
- expect(result.current.showConfigureModal).toBe(false);
- expect(result.current.showVerifyModal).toBe(false);
- expect(result.current.showDeleteModal).toBe(false);
- });
-
- it('should handle domain creation with null return value', async () => {
- const mockOnCreateDomain = vi.fn().mockResolvedValue(null);
- const options = createMockOptions({ onCreateDomain: mockOnCreateDomain });
-
- const { result } = renderHook(() => useDomainTableLogic(options));
-
- await act(async () => {
- await result.current.handleCreate('test.com');
- });
-
- expect(result.current.selectedDomain).toBeNull();
- expect(result.current.showCreateModal).toBe(false);
- expect(result.current.showVerifyModal).toBe(true);
- });
-
- it('should handle various domain statuses in handleConfigureClick', async () => {
- const { result } = renderHook(() => useDomainTableLogic(mockOptions));
-
- // Test with 'failed' status
- const failedDomain = createMockDomain({ status: 'failed' });
- await act(async () => {
- await result.current.handleConfigureClick(failedDomain);
- });
- expect(result.current.showVerifyModal).toBe(true);
-
- // Reset state
- act(() => {
- result.current.setShowVerifyModal(false);
- });
-
- // Test with 'verified' status
- const verifiedDomain = createMockDomain({ status: 'verified' });
- await act(async () => {
- await result.current.handleConfigureClick(verifiedDomain);
- });
- expect(result.current.showConfigureModal).toBe(true);
- });
- });
-
- describe('Callback Dependencies', () => {
- it('should update callbacks when dependencies change', () => {
- const { result, rerender } = renderHook((options) => useDomainTableLogic(options), {
- initialProps: mockOptions,
- });
-
- const initialHandleCreate = result.current.handleCreate;
-
- // Update the options with a new onCreateDomain function
- const newOptions = createMockOptions({
- onCreateDomain: vi.fn(),
- });
-
- rerender(newOptions);
-
- // The callback should be different due to dependency change
- expect(result.current.handleCreate).not.toBe(initialHandleCreate);
- });
- });
-});
diff --git a/packages/react/src/hooks/my-organization/__tests__/use-domain-table.test.ts b/packages/react/src/hooks/my-organization/__tests__/use-domain-table.test.ts
index 2ea12ea98..e80a5767c 100644
--- a/packages/react/src/hooks/my-organization/__tests__/use-domain-table.test.ts
+++ b/packages/react/src/hooks/my-organization/__tests__/use-domain-table.test.ts
@@ -1,837 +1,407 @@
-import type {
- CreateOrganizationDomainRequestContent,
- EnhancedTranslationFunction,
-} from '@auth0/universal-components-core';
-import { BusinessError } from '@auth0/universal-components-core';
-import { renderHook, waitFor } from '@testing-library/react';
-import { describe, it, expect, beforeEach, vi } from 'vitest';
-
-import { useDomainTable } from '@/hooks/my-organization/use-domain-table';
-import * as useCoreClientModule from '@/hooks/shared/use-core-client';
-import * as useTranslatorModule from '@/hooks/shared/use-translator';
-import {
- mockCore,
- createMockDomain,
- createMockIdentityProvider,
- createMockI18nService,
-} from '@/tests/utils';
-import { createTestQueryClientWrapper } from '@/tests/utils/test-provider';
-import type { UseDomainTableOptions } from '@/types/my-organization/domain-management/domain-table-types';
-
-// ===== Mock packages =====
-
-const { initMockCoreClient } = mockCore();
-
-// ===== Mock Data =====
-
-const createMockOptions = (overrides?: Partial): UseDomainTableOptions => ({
- createAction: {
- onBefore: vi.fn().mockReturnValue(true),
- onAfter: vi.fn(),
- },
- deleteAction: {
- onBefore: vi.fn().mockReturnValue(true),
- onAfter: vi.fn(),
- },
- verifyAction: {
- onBefore: vi.fn().mockReturnValue(true),
- onAfter: vi.fn(),
- },
- associateToProviderAction: {
- onBefore: vi.fn().mockReturnValue(true),
- onAfter: vi.fn(),
- },
- deleteFromProviderAction: {
- onBefore: vi.fn().mockReturnValue(true),
- onAfter: vi.fn(),
- },
- customMessages: {},
- ...overrides,
-});
+import { renderHook, act } from '@testing-library/react';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
-const renderUseDomainTable = (options: UseDomainTableOptions) => {
- const { wrapper, queryClient } = createTestQueryClientWrapper();
- return {
- queryClient,
- ...renderHook(() => useDomainTable(options), { wrapper }),
- };
-};
+import { useDomainTable } from '../use-domain-table';
-// ===== Tests =====
+import { useDomainTableService } from '@/hooks/my-organization/shared/services/use-domain-table-service';
+import { mockToast, createMockDomainTableServiceReturn } from '@/tests/utils';
-describe('useDomainTable', () => {
- let mockCoreClient: ReturnType;
- let mockOptions: UseDomainTableOptions;
- let mockT: EnhancedTranslationFunction;
+const mockHandleError = vi.fn();
- beforeEach(() => {
- vi.clearAllMocks();
+vi.mock('@/hooks/shared/use-translator', () => ({
+ useTranslator: () => ({ t: (key: string) => key }),
+}));
- mockCoreClient = initMockCoreClient();
- mockOptions = createMockOptions();
- mockT = createMockI18nService().translator('my-organization');
+vi.mock('@/hooks/shared/use-error-handler', () => ({
+ useErrorHandler: () => mockHandleError,
+}));
- vi.spyOn(useCoreClientModule, 'useCoreClient').mockReturnValue({
- coreClient: mockCoreClient,
- });
+mockToast();
- vi.spyOn(useTranslatorModule, 'useTranslator').mockReturnValue({
- t: mockT,
- changeLanguage: vi.fn(),
- currentLanguage: 'en',
- fallbackLanguage: 'en',
- });
- });
-
- describe('Initial State', () => {
- it('should initialize with correct default state', async () => {
- const { result } = renderUseDomainTable(mockOptions);
-
- // Initial state before query completes
- expect(result.current.domains).toEqual([]);
- expect(result.current.providers).toEqual([]);
- expect(result.current.isCreating).toBe(false);
- expect(result.current.isDeleting).toBe(false);
- expect(result.current.isVerifying).toBe(false);
- expect(result.current.isLoadingProviders).toBe(false);
-
- // Wait for initial query to complete
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
- });
+vi.mock('@/hooks/my-organization/shared/services/use-domain-table-service', () => ({
+ useDomainTableService: vi.fn(),
+}));
- it('should provide all expected functions', () => {
- const { result } = renderUseDomainTable(mockOptions);
-
- expect(typeof result.current.fetchDomains).toBe('function');
- expect(typeof result.current.fetchProviders).toBe('function');
- expect(typeof result.current.onCreateDomain).toBe('function');
- expect(typeof result.current.onVerifyDomain).toBe('function');
- expect(typeof result.current.onDeleteDomain).toBe('function');
- expect(typeof result.current.onAssociateToProvider).toBe('function');
- expect(typeof result.current.onDeleteFromProvider).toBe('function');
- });
- });
+const mockUseDomainTableService = vi.mocked(useDomainTableService);
- describe('fetchDomains', () => {
- it('should fetch domains successfully', async () => {
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.fetchDomains();
+describe('useDomainTable', () => {
+ const mockDomain = {
+ id: 'domain_abc123',
+ org_id: 'org_123',
+ domain: 'test.com',
+ status: 'pending' as const,
+ verification_txt: 'txt',
+ verification_host: 'host',
+ };
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
+ const verifiedDomain = { ...mockDomain, status: 'verified' as const };
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
- ).toHaveBeenCalled();
- });
+ const mockProvider = {
+ id: 'con_abc123',
+ name: 'TestIDP',
+ display_name: 'Test IDP',
+ options: {},
+ strategy: 'waad' as const,
+ };
- it('should handle fetchDomains error and reset loading state', async () => {
- const error = new Error('Network error');
- mockCoreClient.getMyOrganizationApiClient().organization.domains.list = vi
- .fn()
- .mockRejectedValue(error);
+ const defaultOptions = {
+ createAction: { onBefore: vi.fn(() => true), onAfter: vi.fn() },
+ deleteAction: { onBefore: vi.fn(() => true), onAfter: vi.fn() },
+ verifyAction: { onBefore: vi.fn(() => true), onAfter: vi.fn() },
+ associateToProviderAction: { onBefore: vi.fn(() => true), onAfter: vi.fn() },
+ deleteFromProviderAction: { onBefore: vi.fn(() => true), onAfter: vi.fn() },
+ customMessages: {},
+ };
- const { result } = renderUseDomainTable(mockOptions);
+ beforeEach(() => {
+ vi.clearAllMocks();
+ mockUseDomainTableService.mockReturnValue(createMockDomainTableServiceReturn());
+ });
- await result.current.fetchDomains();
+ it('should return correct initial state', () => {
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
+
+ expect(result.current.showCreateModal).toBe(false);
+ expect(result.current.showConfigureModal).toBe(false);
+ expect(result.current.showVerifyModal).toBe(false);
+ expect(result.current.showDeleteModal).toBe(false);
+ expect(result.current.verifyError).toBeUndefined();
+ expect(result.current.selectedDomain).toBeNull();
+ expect(result.current.domains).toEqual([]);
+ expect(result.current.providers).toEqual([]);
+ });
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
+ it('should show create modal on handleCreateClick', () => {
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(result.current.isFetching).toBe(false);
+ act(() => {
+ result.current.handleCreateClick();
});
- it('should handle empty domains response', async () => {
- const { result } = renderUseDomainTable(mockOptions);
+ expect(result.current.showCreateModal).toBe(true);
+ });
- await result.current.fetchDomains();
+ it('should create domain, show toast, and open verify modal on handleCreate', async () => {
+ const mockOnCreateDomain = vi.fn().mockResolvedValue(mockDomain);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onCreateDomain: mockOnCreateDomain }),
+ );
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(result.current.domains).toEqual([]);
+ await act(async () => {
+ await result.current.handleCreate('test.com');
});
- it('should read from cache without refetching when fetchDomains is called', async () => {
- const { result } = renderUseDomainTable(mockOptions);
-
- // Wait for initial fetch to complete
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
+ expect(mockOnCreateDomain).toHaveBeenCalledWith({ domain: 'test.com' });
+ expect(result.current.selectedDomain).toEqual(mockDomain);
+ expect(result.current.showCreateModal).toBe(false);
+ expect(result.current.showVerifyModal).toBe(true);
+ });
- const initialCallCount = vi.mocked(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
- ).mock.calls.length;
+ it('should handle create error on handleCreate', async () => {
+ const error = new Error('Create failed');
+ const mockOnCreateDomain = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onCreateDomain: mockOnCreateDomain }),
+ );
- // Call fetchDomains - should read from cache without triggering refetch
- await result.current.fetchDomains();
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- // Should not trigger additional API calls
- expect(
- vi.mocked(mockCoreClient.getMyOrganizationApiClient().organization.domains.list).mock.calls
- .length,
- ).toBe(initialCallCount);
+ await act(async () => {
+ await result.current.handleCreate('test.com');
});
- it('should refetch when data is invalidated', async () => {
- const { result, queryClient } = renderUseDomainTable(mockOptions);
-
- // Wait for initial fetch to complete
- await waitFor(() => {
- expect(result.current.isFetching).toBe(false);
- });
-
- const initialCallCount = vi.mocked(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
- ).mock.calls.length;
-
- // Invalidate the query
- await queryClient.invalidateQueries({ queryKey: ['domains', 'list'] });
-
- // Call fetchDomains
- await result.current.fetchDomains();
-
- // Should call the API again due to invalidation
- await waitFor(() => {
- expect(
- vi.mocked(mockCoreClient.getMyOrganizationApiClient().organization.domains.list).mock
- .calls.length,
- ).toBeGreaterThan(initialCallCount);
- });
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_create.error',
});
});
- describe('fetchProviders', () => {
- it('should fetch providers with correct association status', async () => {
- const mockDomain = createMockDomain();
- const provider1 = createMockIdentityProvider({
- id: 'provider-1',
- display_name: 'Provider 1',
- domains: [mockDomain.domain],
- });
- const provider2 = createMockIdentityProvider({
- id: 'provider-2',
- display_name: 'Provider 2',
- domains: [],
- });
- const provider3 = createMockIdentityProvider({
- id: 'provider-3',
- display_name: 'Provider 3',
- domains: [mockDomain.domain],
- });
-
- // Mock all providers response - domains field indicates association
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: [provider1, provider2, provider3],
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.fetchProviders(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
- ).toHaveBeenCalled();
-
- // Verify the providers are correctly matched with association status
- expect(result.current.providers).toHaveLength(3);
-
- // Provider 1 should be associated
- const resultProvider1 = result.current.providers.find((p) => p.id === 'provider-1');
- expect(resultProvider1).toBeDefined();
- expect(resultProvider1!.is_associated).toBe(true);
- expect(resultProvider1!.display_name).toBe('Provider 1');
-
- // Provider 2 should NOT be associated
- const resultProvider2 = result.current.providers.find((p) => p.id === 'provider-2');
- expect(resultProvider2).toBeDefined();
- expect(resultProvider2!.is_associated).toBe(false);
- expect(resultProvider2!.display_name).toBe('Provider 2');
-
- // Provider 3 should be associated
- const resultProvider3 = result.current.providers.find((p) => p.id === 'provider-3');
- expect(resultProvider3).toBeDefined();
- expect(resultProvider3!.is_associated).toBe(true);
- expect(resultProvider3!.display_name).toBe('Provider 3');
- });
+ it('should verify domain and close verify modal on handleVerify success', async () => {
+ const mockOnVerifyDomain = vi.fn().mockResolvedValue(true);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- it('should handle providers with no associations', async () => {
- const mockDomain = createMockDomain();
- const provider1 = createMockIdentityProvider({
- id: 'provider-1',
- display_name: 'Provider 1',
- domains: [],
- });
- const provider2 = createMockIdentityProvider({
- id: 'provider-2',
- display_name: 'Provider 2',
- domains: [],
- });
-
- // Mock all providers response - no domains associated
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: [provider1, provider2],
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.fetchProviders(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
-
- // All providers should have is_associated = false
- expect(result.current.providers).toHaveLength(2);
- result.current.providers.forEach((provider) => {
- expect(provider.is_associated).toBe(false);
- });
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- it('should handle all providers being associated', async () => {
- const mockDomain = createMockDomain();
- const provider1 = createMockIdentityProvider({
- id: 'provider-1',
- display_name: 'Provider 1',
- domains: [mockDomain.domain],
- });
- const provider2 = createMockIdentityProvider({
- id: 'provider-2',
- display_name: 'Provider 2',
- domains: [mockDomain.domain],
- });
-
- // Mock all providers response - all associated via domains field
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: [provider1, provider2],
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.fetchProviders(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
-
- // All providers should have is_associated = true
- expect(result.current.providers).toHaveLength(2);
- result.current.providers.forEach((provider) => {
- expect(provider.is_associated).toBe(true);
- });
+ await act(async () => {
+ await result.current.handleVerify(mockDomain);
});
- it('should handle fetchProviders error and reset loading state', async () => {
- const mockDomain = createMockDomain();
- const error = new Error('Network error');
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockRejectedValue(error);
+ expect(mockOnVerifyDomain).toHaveBeenCalledWith(mockDomain);
+ expect(result.current.showVerifyModal).toBe(false);
+ });
- const { result } = renderUseDomainTable(mockOptions);
+ it('should set verify error on handleVerify failure', async () => {
+ const mockOnVerifyDomain = vi.fn().mockResolvedValue(false);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- await expect(result.current.fetchProviders(mockDomain)).rejects.toThrow('Network error');
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
+ await act(async () => {
+ await result.current.handleVerify(mockDomain);
});
- it('should handle null/undefined responses gracefully', async () => {
- const mockDomain = createMockDomain();
-
- // Mock null response
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: null,
- });
-
- const { result } = renderUseDomainTable(mockOptions);
+ expect(result.current.verifyError).toBe('domain_verify.modal.errors.verification_failed');
+ });
- await result.current.fetchProviders(mockDomain);
+ it('should handle verify error on handleVerify', async () => {
+ const error = new Error('Verify failed');
+ const mockOnVerifyDomain = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- // Should handle null gracefully and return empty array
- expect(result.current.providers).toEqual([]);
+ await act(async () => {
+ await result.current.handleVerify(mockDomain);
});
- it('should use ensureQueryData to fetch providers', async () => {
- const mockDomain = createMockDomain();
- const provider1 = createMockIdentityProvider({
- id: 'provider-1',
- display_name: 'Provider 1',
- domains: [mockDomain.domain],
- });
-
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: [provider1],
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.fetchProviders(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
-
- expect(result.current.providers).toHaveLength(1);
- const firstProvider = result.current.providers[0];
- expect(firstProvider).toBeDefined();
- expect(firstProvider!.is_associated).toBe(true);
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_verify.error',
});
+ });
- it('should fetch providers from cache via ensureQueryData', async () => {
- const mockDomain = createMockDomain();
- const provider1 = createMockIdentityProvider({
- id: 'provider-1',
- display_name: 'Provider 1',
- domains: [mockDomain.domain],
- });
-
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
- .fn()
- .mockResolvedValue({
- identity_providers: [provider1],
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- // First fetch
- await result.current.fetchProviders(mockDomain);
+ it('should delete domain and close modals on handleDelete', async () => {
+ const mockOnDeleteDomain = vi.fn().mockResolvedValue(undefined);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onDeleteDomain: mockOnDeleteDomain }),
+ );
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- const initialApiCallCount = vi.mocked(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
- ).mock.calls.length;
+ await act(async () => {
+ await result.current.handleDelete(mockDomain);
+ });
- // Second fetch - should use cached data since it's fresh
- await result.current.fetchProviders(mockDomain);
+ expect(mockOnDeleteDomain).toHaveBeenCalledWith(mockDomain);
+ expect(result.current.showDeleteModal).toBe(false);
+ expect(result.current.showVerifyModal).toBe(false);
+ });
- await waitFor(() => {
- expect(result.current.isLoadingProviders).toBe(false);
- });
+ it('should handle delete error on handleDelete', async () => {
+ const error = new Error('Delete failed');
+ const mockOnDeleteDomain = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onDeleteDomain: mockOnDeleteDomain }),
+ );
- // Verify providers are loaded correctly
- expect(result.current.providers).toHaveLength(1);
- const cachedProvider = result.current.providers[0];
- expect(cachedProvider).toBeDefined();
- expect(cachedProvider!.is_associated).toBe(true);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- // Should use cache if available and fresh (not make additional API calls)
- const finalApiCallCount = vi.mocked(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
- ).mock.calls.length;
+ await act(async () => {
+ await result.current.handleDelete(mockDomain);
+ });
- expect(finalApiCallCount).toBe(initialApiCallCount);
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_delete.error',
});
});
- describe('onCreateDomain', () => {
- it('should create domain successfully with callbacks', async () => {
- const mockDomain = createMockDomain();
- const createData: CreateOrganizationDomainRequestContent = { domain: mockDomain.domain };
-
- const { result } = renderUseDomainTable(mockOptions);
+ it('should associate domain to provider on handleToggleSwitch with true', async () => {
+ const mockOnAssociateToProvider = vi.fn().mockResolvedValue(undefined);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onAssociateToProvider: mockOnAssociateToProvider }),
+ );
- await result.current.onCreateDomain(createData);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
-
- expect(mockOptions.createAction!.onBefore).toHaveBeenCalledWith(createData);
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
- ).toHaveBeenCalledWith(createData);
+ await act(async () => {
+ await result.current.handleToggleSwitch(mockDomain, mockProvider, true);
});
- it('should handle onBefore callback returning false', async () => {
- const createData: CreateOrganizationDomainRequestContent = { domain: 'test.com' };
- const mockOptionsWithFalseBefore = createMockOptions({
- createAction: {
- onBefore: vi.fn().mockReturnValue(false),
- onAfter: vi.fn(),
- },
- });
+ expect(mockOnAssociateToProvider).toHaveBeenCalledWith(mockDomain, mockProvider);
+ });
- const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+ it('should delete domain from provider on handleToggleSwitch with false', async () => {
+ const mockOnDeleteFromProvider = vi.fn().mockResolvedValue(undefined);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onDeleteFromProvider: mockOnDeleteFromProvider }),
+ );
- await expect(result.current.onCreateDomain(createData)).rejects.toThrow(BusinessError);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
- ).not.toHaveBeenCalled();
+ await act(async () => {
+ await result.current.handleToggleSwitch(mockDomain, mockProvider, false);
});
- it('should handle create domain API error', async () => {
- const createData: CreateOrganizationDomainRequestContent = { domain: 'test.com' };
- const error = new Error('API error');
- mockCoreClient.getMyOrganizationApiClient().organization.domains.create = vi
- .fn()
- .mockRejectedValue(error);
-
- const { result } = renderUseDomainTable(mockOptions);
+ expect(mockOnDeleteFromProvider).toHaveBeenCalledWith(mockDomain, mockProvider);
+ });
- await expect(result.current.onCreateDomain(createData)).rejects.toThrow('API error');
+ it('should handle associate to provider error on handleToggleSwitch', async () => {
+ const error = new Error('Associate failed');
+ const mockOnAssociateToProvider = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onAssociateToProvider: mockOnAssociateToProvider }),
+ );
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(result.current.isCreating).toBe(false);
+ await act(async () => {
+ await result.current.handleToggleSwitch(mockDomain, mockProvider, true);
});
- it('should work without onBefore and onAfter callbacks', async () => {
- const mockDomain = createMockDomain();
- const createData: CreateOrganizationDomainRequestContent = { domain: mockDomain.domain };
- const mockOptionsWithoutCallbacks = createMockOptions({
- createAction: undefined,
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
-
- await result.current.onCreateDomain(createData);
-
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
- ).toHaveBeenCalledWith(createData);
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_associate_provider.error',
});
});
- describe('onVerifyDomain', () => {
- it('should verify domain successfully and return true', async () => {
- const mockDomain = createMockDomain();
- const { result } = renderUseDomainTable(mockOptions);
-
- const isVerified = await result.current.onVerifyDomain(mockDomain);
+ it('should handle delete from provider error on handleToggleSwitch', async () => {
+ const error = new Error('Delete from provider failed');
+ const mockOnDeleteFromProvider = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onDeleteFromProvider: mockOnDeleteFromProvider }),
+ );
- await waitFor(() => {
- expect(result.current.isVerifying).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(mockOptions.verifyAction!.onBefore).toHaveBeenCalledWith(mockDomain);
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
- ).toHaveBeenCalledWith(mockDomain.id);
- expect(isVerified).toBe(true);
+ await act(async () => {
+ await result.current.handleToggleSwitch(mockDomain, mockProvider, false);
});
- it('should verify domain and return false when status is not verified', async () => {
- const mockDomain = createMockDomain();
- mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create = vi
- .fn()
- .mockResolvedValue({
- status: 'pending',
- });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- const isVerified = await result.current.onVerifyDomain(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isVerifying).toBe(false);
- });
-
- expect(isVerified).toBe(false);
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_delete_provider.error',
});
+ });
- it('should handle onBefore callback returning false', async () => {
- const mockDomain = createMockDomain();
- const mockOptionsWithFalseBefore = createMockOptions({
- verifyAction: {
- onBefore: vi.fn().mockReturnValue(false),
- onAfter: vi.fn(),
- },
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+ it('should close verify modal and clear error on handleCloseVerifyModal', async () => {
+ const mockOnVerifyDomain = vi.fn().mockResolvedValue(false);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- await expect(result.current.onVerifyDomain(mockDomain)).rejects.toThrow(BusinessError);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
- ).not.toHaveBeenCalled();
+ await act(async () => {
+ await result.current.handleVerify(mockDomain);
});
+ expect(result.current.verifyError).toBeDefined();
- it('should work without onBefore and onAfter callbacks', async () => {
- const mockDomain = createMockDomain();
- const mockOptionsWithoutCallbacks = createMockOptions({
- verifyAction: undefined,
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
-
- const isVerified = await result.current.onVerifyDomain(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isVerifying).toBe(false);
- });
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
- ).toHaveBeenCalledWith(mockDomain.id);
- expect(isVerified).toBe(true);
+ act(() => {
+ result.current.handleCloseVerifyModal();
});
- });
-
- describe('onDeleteDomain', () => {
- it('should delete domain successfully with callbacks', async () => {
- const mockDomain = createMockDomain();
- const { result } = renderUseDomainTable(mockOptions);
- await result.current.onDeleteDomain(mockDomain);
+ expect(result.current.showVerifyModal).toBe(false);
+ expect(result.current.verifyError).toBeUndefined();
+ });
- await waitFor(() => {
- expect(result.current.isDeleting).toBe(false);
- });
+ it('should show verify modal for unverified domain on handleConfigureClick', async () => {
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(mockOptions.deleteAction!.onBefore).toHaveBeenCalledWith(mockDomain);
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
- ).toHaveBeenCalledWith(mockDomain.id);
- expect(mockOptions.deleteAction!.onAfter).toHaveBeenCalledWith(mockDomain);
+ await act(async () => {
+ await result.current.handleConfigureClick(mockDomain);
});
- it('should handle onBefore callback returning false', async () => {
- const mockDomain = createMockDomain();
- const mockOptionsWithFalseBefore = createMockOptions({
- deleteAction: {
- onBefore: vi.fn().mockReturnValue(false),
- onAfter: vi.fn(),
- },
- });
+ expect(result.current.selectedDomain).toEqual(mockDomain);
+ expect(result.current.showVerifyModal).toBe(true);
+ });
- const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+ it('should fetch providers and show configure modal for verified domain on handleConfigureClick', async () => {
+ const mockFetchProviders = vi.fn().mockResolvedValue(undefined);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ fetchProviders: mockFetchProviders }),
+ );
- await expect(result.current.onDeleteDomain(mockDomain)).rejects.toThrow(BusinessError);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
- ).not.toHaveBeenCalled();
+ await act(async () => {
+ await result.current.handleConfigureClick(verifiedDomain);
});
- it('should work without onBefore and onAfter callbacks', async () => {
- const mockDomain = createMockDomain();
- const mockOptionsWithoutCallbacks = createMockOptions({
- deleteAction: undefined,
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
-
- await result.current.onDeleteDomain(mockDomain);
-
- await waitFor(() => {
- expect(result.current.isDeleting).toBe(false);
- });
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
- ).toHaveBeenCalledWith(mockDomain.id);
- });
+ expect(result.current.selectedDomain).toEqual(verifiedDomain);
+ expect(mockFetchProviders).toHaveBeenCalledWith(verifiedDomain);
+ expect(result.current.showConfigureModal).toBe(true);
});
- describe('onAssociateToProvider', () => {
- it('should associate domain to provider successfully', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
+ it('should handle fetchProviders error on handleConfigureClick', async () => {
+ const error = new Error('Fetch providers failed');
+ const mockFetchProviders = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ fetchProviders: mockFetchProviders }),
+ );
- const { result } = renderUseDomainTable(mockOptions);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- await result.current.onAssociateToProvider(mockDomain, mockProvider);
-
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
-
- expect(mockOptions.associateToProviderAction!.onBefore).toHaveBeenCalledWith(
- mockDomain,
- mockProvider,
- );
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
- ).toHaveBeenCalledWith(mockProvider.id, { domain: mockDomain.domain });
+ await act(async () => {
+ await result.current.handleConfigureClick(verifiedDomain);
});
- it('should handle onBefore callback returning false', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const mockOptionsWithFalseBefore = createMockOptions({
- associateToProviderAction: {
- onBefore: vi.fn().mockReturnValue(false),
- onAfter: vi.fn(),
- },
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
-
- await expect(result.current.onAssociateToProvider(mockDomain, mockProvider)).rejects.toThrow(
- BusinessError,
- );
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
- ).not.toHaveBeenCalled();
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.fetch_providers_error',
});
+ });
- it('should work without onBefore and onAfter callbacks', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const mockOptionsWithoutCallbacks = createMockOptions({
- associateToProviderAction: undefined,
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
-
- await result.current.onAssociateToProvider(mockDomain, mockProvider);
+ it('should verify, fetch providers, and show configure modal on handleVerifyClick success', async () => {
+ const mockOnVerifyDomain = vi.fn().mockResolvedValue(true);
+ const mockFetchProviders = vi.fn().mockResolvedValue(undefined);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({
+ onVerifyDomain: mockOnVerifyDomain,
+ fetchProviders: mockFetchProviders,
+ }),
+ );
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
- ).toHaveBeenCalledWith(mockProvider.id, { domain: mockDomain.domain });
+ await act(async () => {
+ await result.current.handleVerifyClick(mockDomain);
});
- });
- describe('onDeleteFromProvider', () => {
- it('should delete domain from provider successfully', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.onDeleteFromProvider(mockDomain, mockProvider);
+ expect(result.current.selectedDomain).toEqual(mockDomain);
+ expect(mockOnVerifyDomain).toHaveBeenCalledWith(mockDomain);
+ expect(mockFetchProviders).toHaveBeenCalledWith(mockDomain);
+ expect(result.current.showConfigureModal).toBe(true);
+ });
- await waitFor(() => {
- expect(result.current.isDeleting).toBe(false);
- });
+ it('should show error toast on handleVerifyClick failure', async () => {
+ const mockOnVerifyDomain = vi.fn().mockResolvedValue(false);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- expect(mockOptions.deleteFromProviderAction!.onBefore).toHaveBeenCalledWith(
- mockDomain,
- mockProvider,
- );
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
- ).toHaveBeenCalledWith(mockProvider.id, mockDomain.domain);
- });
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- it('should handle onBefore callback returning false', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const mockOptionsWithFalseBefore = createMockOptions({
- deleteFromProviderAction: {
- onBefore: vi.fn().mockReturnValue(false),
- onAfter: vi.fn(),
- },
- });
-
- const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
-
- await expect(result.current.onDeleteFromProvider(mockDomain, mockProvider)).rejects.toThrow(
- BusinessError,
- );
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
- ).not.toHaveBeenCalled();
+ await act(async () => {
+ await result.current.handleVerifyClick(mockDomain);
});
- it('should work without onBefore and onAfter callbacks', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider();
- const mockOptionsWithoutCallbacks = createMockOptions({
- deleteFromProviderAction: undefined,
- });
+ expect(result.current.selectedDomain).toEqual(mockDomain);
+ });
- const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+ it('should handle error on handleVerifyClick', async () => {
+ const error = new Error('Verify click failed');
+ const mockOnVerifyDomain = vi.fn().mockRejectedValue(error);
+ mockUseDomainTableService.mockReturnValue(
+ createMockDomainTableServiceReturn({ onVerifyDomain: mockOnVerifyDomain }),
+ );
- await result.current.onDeleteFromProvider(mockDomain, mockProvider);
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- await waitFor(() => {
- expect(result.current.isDeleting).toBe(false);
- });
+ await act(async () => {
+ await result.current.handleVerifyClick(mockDomain);
+ });
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
- ).toHaveBeenCalledWith(mockProvider.id, mockDomain.domain);
+ expect(mockHandleError).toHaveBeenCalledWith(error, {
+ fallbackMessage: 'domain_table.notifications.domain_verify.error',
});
});
- describe('Edge Cases and Integration', () => {
- it('should handle provider with undefined id in onAssociateToProvider', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider({ id: undefined });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.onAssociateToProvider(mockDomain, mockProvider);
-
- await waitFor(() => {
- expect(result.current.isCreating).toBe(false);
- });
+ it('should set selected domain, close verify modal, and show delete modal on handleDeleteClick', () => {
+ const { result } = renderHook(() => useDomainTable(defaultOptions));
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
- ).toHaveBeenCalledWith(undefined, { domain: mockDomain.domain });
+ act(() => {
+ result.current.setShowVerifyModal(true);
});
- it('should handle provider with undefined id in onDeleteFromProvider', async () => {
- const mockDomain = createMockDomain();
- const mockProvider = createMockIdentityProvider({ id: undefined });
-
- const { result } = renderUseDomainTable(mockOptions);
-
- await result.current.onDeleteFromProvider(mockDomain, mockProvider);
-
- await waitFor(() => {
- expect(result.current.isDeleting).toBe(false);
- });
-
- expect(
- mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
- ).toHaveBeenCalledWith(undefined, mockDomain.domain);
+ act(() => {
+ result.current.handleDeleteClick(mockDomain);
});
- it('should call useTranslator with correct parameters', () => {
- const useTranslatorSpy = vi.spyOn(useTranslatorModule, 'useTranslator');
- renderUseDomainTable(mockOptions);
-
- expect(useTranslatorSpy).toHaveBeenCalledWith(
- 'domain_management.domain_table.notifications',
- {},
- );
- });
+ expect(result.current.selectedDomain).toEqual(mockDomain);
+ expect(result.current.showVerifyModal).toBe(false);
+ expect(result.current.showDeleteModal).toBe(true);
});
});
diff --git a/packages/react/src/hooks/my-organization/shared/__tests__/use-domain-table-service.test.ts b/packages/react/src/hooks/my-organization/shared/__tests__/use-domain-table-service.test.ts
new file mode 100644
index 000000000..371f05594
--- /dev/null
+++ b/packages/react/src/hooks/my-organization/shared/__tests__/use-domain-table-service.test.ts
@@ -0,0 +1,807 @@
+import type {
+ CreateOrganizationDomainRequestContent,
+ EnhancedTranslationFunction,
+} from '@auth0/universal-components-core';
+import { BusinessError } from '@auth0/universal-components-core';
+import { renderHook, waitFor } from '@testing-library/react';
+import { describe, it, expect, beforeEach, vi } from 'vitest';
+
+import { useDomainTableService } from '@/hooks/my-organization/shared/services/use-domain-table-service';
+import * as useCoreClientModule from '@/hooks/shared/use-core-client';
+import * as useTranslatorModule from '@/hooks/shared/use-translator';
+import {
+ mockCore,
+ createMockDomain,
+ createMockIdentityProvider,
+ createMockI18nService,
+ createMockDomainTableServiceOptions,
+} from '@/tests/utils';
+import { createTestQueryClientWrapper } from '@/tests/utils/test-provider';
+import type { UseDomainTableServiceOptions } from '@/types/my-organization/domain-management/domain-table-types';
+
+const { initMockCoreClient } = mockCore();
+
+const renderUseDomainTable = (options: UseDomainTableServiceOptions) => {
+ const { wrapper, queryClient } = createTestQueryClientWrapper();
+ return {
+ queryClient,
+ ...renderHook(() => useDomainTableService(options), { wrapper }),
+ };
+};
+
+describe('useDomainTableService', () => {
+ let mockCoreClient: ReturnType;
+ let mockOptions: UseDomainTableServiceOptions;
+ let mockT: EnhancedTranslationFunction;
+
+ beforeEach(() => {
+ vi.clearAllMocks();
+
+ mockCoreClient = initMockCoreClient();
+ mockOptions = createMockDomainTableServiceOptions();
+ mockT = createMockI18nService().translator('my-organization');
+
+ vi.spyOn(useCoreClientModule, 'useCoreClient').mockReturnValue({
+ coreClient: mockCoreClient,
+ });
+
+ vi.spyOn(useTranslatorModule, 'useTranslator').mockReturnValue({
+ t: mockT,
+ changeLanguage: vi.fn(),
+ currentLanguage: 'en',
+ fallbackLanguage: 'en',
+ });
+ });
+
+ describe('Initial State', () => {
+ it('should initialize with correct default state', async () => {
+ const { result } = renderUseDomainTable(mockOptions);
+
+ // Initial state before query completes
+ expect(result.current.domains).toEqual([]);
+ expect(result.current.providers).toEqual([]);
+ expect(result.current.isCreating).toBe(false);
+ expect(result.current.isDeleting).toBe(false);
+ expect(result.current.isVerifying).toBe(false);
+ expect(result.current.isLoadingProviders).toBe(false);
+
+ // Wait for initial query to complete
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+ });
+
+ it('should provide all expected functions', () => {
+ const { result } = renderUseDomainTable(mockOptions);
+
+ expect(typeof result.current.fetchDomains).toBe('function');
+ expect(typeof result.current.fetchProviders).toBe('function');
+ expect(typeof result.current.onCreateDomain).toBe('function');
+ expect(typeof result.current.onVerifyDomain).toBe('function');
+ expect(typeof result.current.onDeleteDomain).toBe('function');
+ expect(typeof result.current.onAssociateToProvider).toBe('function');
+ expect(typeof result.current.onDeleteFromProvider).toBe('function');
+ });
+ });
+
+ describe('fetchDomains', () => {
+ it('should fetch domains successfully', async () => {
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchDomains();
+
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
+ ).toHaveBeenCalled();
+ });
+
+ it('should handle fetchDomains error and reset loading state', async () => {
+ const error = new Error('Network error');
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.list = vi
+ .fn()
+ .mockRejectedValue(error);
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchDomains();
+
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ it('should handle empty domains response', async () => {
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchDomains();
+
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ expect(result.current.domains).toEqual([]);
+ });
+
+ it('should read from cache without refetching when fetchDomains is called', async () => {
+ const { result } = renderUseDomainTable(mockOptions);
+
+ // Wait for initial fetch to complete
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ const initialCallCount = vi.mocked(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
+ ).mock.calls.length;
+
+ // Call fetchDomains - should read from cache without triggering refetch
+ await result.current.fetchDomains();
+
+ // Should not trigger additional API calls
+ expect(
+ vi.mocked(mockCoreClient.getMyOrganizationApiClient().organization.domains.list).mock.calls
+ .length,
+ ).toBe(initialCallCount);
+ });
+
+ it('should refetch when data is invalidated', async () => {
+ const { result, queryClient } = renderUseDomainTable(mockOptions);
+
+ // Wait for initial fetch to complete
+ await waitFor(() => {
+ expect(result.current.isFetching).toBe(false);
+ });
+
+ const initialCallCount = vi.mocked(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.list,
+ ).mock.calls.length;
+
+ // Invalidate the query
+ await queryClient.invalidateQueries({ queryKey: ['domains', 'list'] });
+
+ // Call fetchDomains
+ await result.current.fetchDomains();
+
+ // Should call the API again due to invalidation
+ await waitFor(() => {
+ expect(
+ vi.mocked(mockCoreClient.getMyOrganizationApiClient().organization.domains.list).mock
+ .calls.length,
+ ).toBeGreaterThan(initialCallCount);
+ });
+ });
+ });
+
+ describe('fetchProviders', () => {
+ it('should fetch providers with correct association status', async () => {
+ const mockDomain = createMockDomain();
+ const provider1 = createMockIdentityProvider({
+ id: 'provider-1',
+ display_name: 'Provider 1',
+ domains: [mockDomain.domain],
+ });
+ const provider2 = createMockIdentityProvider({
+ id: 'provider-2',
+ display_name: 'Provider 2',
+ domains: [],
+ });
+ const provider3 = createMockIdentityProvider({
+ id: 'provider-3',
+ display_name: 'Provider 3',
+ domains: [mockDomain.domain],
+ });
+
+ // Mock all providers response - domains field indicates association
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: [provider1, provider2, provider3],
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
+ ).toHaveBeenCalled();
+
+ // Verify the providers are correctly matched with association status
+ expect(result.current.providers).toHaveLength(3);
+
+ // Provider 1 should be associated
+ const resultProvider1 = result.current.providers.find((p) => p.id === 'provider-1');
+ expect(resultProvider1).toBeDefined();
+ expect(resultProvider1!.is_associated).toBe(true);
+ expect(resultProvider1!.display_name).toBe('Provider 1');
+
+ // Provider 2 should NOT be associated
+ const resultProvider2 = result.current.providers.find((p) => p.id === 'provider-2');
+ expect(resultProvider2).toBeDefined();
+ expect(resultProvider2!.is_associated).toBe(false);
+ expect(resultProvider2!.display_name).toBe('Provider 2');
+
+ // Provider 3 should be associated
+ const resultProvider3 = result.current.providers.find((p) => p.id === 'provider-3');
+ expect(resultProvider3).toBeDefined();
+ expect(resultProvider3!.is_associated).toBe(true);
+ expect(resultProvider3!.display_name).toBe('Provider 3');
+ });
+
+ it('should handle providers with no associations', async () => {
+ const mockDomain = createMockDomain();
+ const provider1 = createMockIdentityProvider({
+ id: 'provider-1',
+ display_name: 'Provider 1',
+ domains: [],
+ });
+ const provider2 = createMockIdentityProvider({
+ id: 'provider-2',
+ display_name: 'Provider 2',
+ domains: [],
+ });
+
+ // Mock all providers response - no domains associated
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: [provider1, provider2],
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ // All providers should have is_associated = false
+ expect(result.current.providers).toHaveLength(2);
+ result.current.providers.forEach((provider) => {
+ expect(provider.is_associated).toBe(false);
+ });
+ });
+
+ it('should handle all providers being associated', async () => {
+ const mockDomain = createMockDomain();
+ const provider1 = createMockIdentityProvider({
+ id: 'provider-1',
+ display_name: 'Provider 1',
+ domains: [mockDomain.domain],
+ });
+ const provider2 = createMockIdentityProvider({
+ id: 'provider-2',
+ display_name: 'Provider 2',
+ domains: [mockDomain.domain],
+ });
+
+ // Mock all providers response - all associated via domains field
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: [provider1, provider2],
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ // All providers should have is_associated = true
+ expect(result.current.providers).toHaveLength(2);
+ result.current.providers.forEach((provider) => {
+ expect(provider.is_associated).toBe(true);
+ });
+ });
+
+ it('should handle fetchProviders error and reset loading state', async () => {
+ const mockDomain = createMockDomain();
+ const error = new Error('Network error');
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockRejectedValue(error);
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await expect(result.current.fetchProviders(mockDomain)).rejects.toThrow('Network error');
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+ });
+
+ it('should handle null/undefined responses gracefully', async () => {
+ const mockDomain = createMockDomain();
+
+ // Mock null response
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: null,
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ // Should handle null gracefully and return empty array
+ expect(result.current.providers).toEqual([]);
+ });
+
+ it('should use ensureQueryData to fetch providers', async () => {
+ const mockDomain = createMockDomain();
+ const provider1 = createMockIdentityProvider({
+ id: 'provider-1',
+ display_name: 'Provider 1',
+ domains: [mockDomain.domain],
+ });
+
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: [provider1],
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ expect(result.current.providers).toHaveLength(1);
+ const firstProvider = result.current.providers[0];
+ expect(firstProvider).toBeDefined();
+ expect(firstProvider!.is_associated).toBe(true);
+ });
+
+ it('should fetch providers from cache via ensureQueryData', async () => {
+ const mockDomain = createMockDomain();
+ const provider1 = createMockIdentityProvider({
+ id: 'provider-1',
+ display_name: 'Provider 1',
+ domains: [mockDomain.domain],
+ });
+
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list = vi
+ .fn()
+ .mockResolvedValue({
+ identity_providers: [provider1],
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ // First fetch
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ const initialApiCallCount = vi.mocked(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
+ ).mock.calls.length;
+
+ // Second fetch - should use cached data since it's fresh
+ await result.current.fetchProviders(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isLoadingProviders).toBe(false);
+ });
+
+ // Verify providers are loaded correctly
+ expect(result.current.providers).toHaveLength(1);
+ const cachedProvider = result.current.providers[0];
+ expect(cachedProvider).toBeDefined();
+ expect(cachedProvider!.is_associated).toBe(true);
+
+ // Should use cache if available and fresh (not make additional API calls)
+ const finalApiCallCount = vi.mocked(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.list,
+ ).mock.calls.length;
+
+ expect(finalApiCallCount).toBe(initialApiCallCount);
+ });
+ });
+
+ describe('onCreateDomain', () => {
+ it('should create domain successfully with callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const createData: CreateOrganizationDomainRequestContent = { domain: mockDomain.domain };
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onCreateDomain(createData);
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(mockOptions.createAction!.onBefore).toHaveBeenCalledWith(createData);
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
+ ).toHaveBeenCalledWith(createData);
+ });
+
+ it('should handle onBefore callback returning false', async () => {
+ const createData: CreateOrganizationDomainRequestContent = { domain: 'test.com' };
+ const mockOptionsWithFalseBefore = createMockDomainTableServiceOptions({
+ createAction: {
+ onBefore: vi.fn().mockReturnValue(false),
+ onAfter: vi.fn(),
+ },
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+
+ await expect(result.current.onCreateDomain(createData)).rejects.toThrow(BusinessError);
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('should handle create domain API error', async () => {
+ const createData: CreateOrganizationDomainRequestContent = { domain: 'test.com' };
+ const error = new Error('API error');
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.create = vi
+ .fn()
+ .mockRejectedValue(error);
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await expect(result.current.onCreateDomain(createData)).rejects.toThrow('API error');
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ it('should work without onBefore and onAfter callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const createData: CreateOrganizationDomainRequestContent = { domain: mockDomain.domain };
+ const mockOptionsWithoutCallbacks = createMockDomainTableServiceOptions({
+ createAction: undefined,
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+
+ await result.current.onCreateDomain(createData);
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.create,
+ ).toHaveBeenCalledWith(createData);
+ });
+ });
+
+ describe('onVerifyDomain', () => {
+ it('should verify domain successfully and return true', async () => {
+ const mockDomain = createMockDomain();
+ const { result } = renderUseDomainTable(mockOptions);
+
+ const isVerified = await result.current.onVerifyDomain(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isVerifying).toBe(false);
+ });
+
+ expect(mockOptions.verifyAction!.onBefore).toHaveBeenCalledWith(mockDomain);
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
+ ).toHaveBeenCalledWith(mockDomain.id);
+ expect(isVerified).toBe(true);
+ });
+
+ it('should verify domain and return false when status is not verified', async () => {
+ const mockDomain = createMockDomain();
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create = vi
+ .fn()
+ .mockResolvedValue({
+ status: 'pending',
+ });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ const isVerified = await result.current.onVerifyDomain(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isVerifying).toBe(false);
+ });
+
+ expect(isVerified).toBe(false);
+ });
+
+ it('should handle onBefore callback returning false', async () => {
+ const mockDomain = createMockDomain();
+ const mockOptionsWithFalseBefore = createMockDomainTableServiceOptions({
+ verifyAction: {
+ onBefore: vi.fn().mockReturnValue(false),
+ onAfter: vi.fn(),
+ },
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+
+ await expect(result.current.onVerifyDomain(mockDomain)).rejects.toThrow(BusinessError);
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('should work without onBefore and onAfter callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const mockOptionsWithoutCallbacks = createMockDomainTableServiceOptions({
+ verifyAction: undefined,
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+
+ const isVerified = await result.current.onVerifyDomain(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isVerifying).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.verify.create,
+ ).toHaveBeenCalledWith(mockDomain.id);
+ expect(isVerified).toBe(true);
+ });
+ });
+
+ describe('onDeleteDomain', () => {
+ it('should delete domain successfully with callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onDeleteDomain(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isDeleting).toBe(false);
+ });
+
+ expect(mockOptions.deleteAction!.onBefore).toHaveBeenCalledWith(mockDomain);
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
+ ).toHaveBeenCalledWith(mockDomain.id);
+ expect(mockOptions.deleteAction!.onAfter).toHaveBeenCalledWith(mockDomain);
+ });
+
+ it('should handle onBefore callback returning false', async () => {
+ const mockDomain = createMockDomain();
+ const mockOptionsWithFalseBefore = createMockDomainTableServiceOptions({
+ deleteAction: {
+ onBefore: vi.fn().mockReturnValue(false),
+ onAfter: vi.fn(),
+ },
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+
+ await expect(result.current.onDeleteDomain(mockDomain)).rejects.toThrow(BusinessError);
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('should work without onBefore and onAfter callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const mockOptionsWithoutCallbacks = createMockDomainTableServiceOptions({
+ deleteAction: undefined,
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+
+ await result.current.onDeleteDomain(mockDomain);
+
+ await waitFor(() => {
+ expect(result.current.isDeleting).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.domains.delete,
+ ).toHaveBeenCalledWith(mockDomain.id);
+ });
+ });
+
+ describe('onAssociateToProvider', () => {
+ it('should associate domain to provider successfully', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onAssociateToProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(mockOptions.associateToProviderAction!.onBefore).toHaveBeenCalledWith(
+ mockDomain,
+ mockProvider,
+ );
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
+ ).toHaveBeenCalledWith(mockProvider.id, { domain: mockDomain.domain });
+ });
+
+ it('should handle onBefore callback returning false', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+ const mockOptionsWithFalseBefore = createMockDomainTableServiceOptions({
+ associateToProviderAction: {
+ onBefore: vi.fn().mockReturnValue(false),
+ onAfter: vi.fn(),
+ },
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+
+ await expect(result.current.onAssociateToProvider(mockDomain, mockProvider)).rejects.toThrow(
+ BusinessError,
+ );
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('should work without onBefore and onAfter callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+ const mockOptionsWithoutCallbacks = createMockDomainTableServiceOptions({
+ associateToProviderAction: undefined,
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+
+ await result.current.onAssociateToProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
+ ).toHaveBeenCalledWith(mockProvider.id, { domain: mockDomain.domain });
+ });
+ });
+
+ describe('onDeleteFromProvider', () => {
+ it('should delete domain from provider successfully', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onDeleteFromProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isDeleting).toBe(false);
+ });
+
+ expect(mockOptions.deleteFromProviderAction!.onBefore).toHaveBeenCalledWith(
+ mockDomain,
+ mockProvider,
+ );
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
+ ).toHaveBeenCalledWith(mockProvider.id, mockDomain.domain);
+ });
+
+ it('should handle onBefore callback returning false', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+ const mockOptionsWithFalseBefore = createMockDomainTableServiceOptions({
+ deleteFromProviderAction: {
+ onBefore: vi.fn().mockReturnValue(false),
+ onAfter: vi.fn(),
+ },
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithFalseBefore);
+
+ await expect(result.current.onDeleteFromProvider(mockDomain, mockProvider)).rejects.toThrow(
+ BusinessError,
+ );
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
+ ).not.toHaveBeenCalled();
+ });
+
+ it('should work without onBefore and onAfter callbacks', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider();
+ const mockOptionsWithoutCallbacks = createMockDomainTableServiceOptions({
+ deleteFromProviderAction: undefined,
+ });
+
+ const { result } = renderUseDomainTable(mockOptionsWithoutCallbacks);
+
+ await result.current.onDeleteFromProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isDeleting).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
+ ).toHaveBeenCalledWith(mockProvider.id, mockDomain.domain);
+ });
+ });
+
+ describe('Edge Cases and Integration', () => {
+ it('should handle provider with undefined id in onAssociateToProvider', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider({ id: undefined });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onAssociateToProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isCreating).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.create,
+ ).toHaveBeenCalledWith(undefined, { domain: mockDomain.domain });
+ });
+
+ it('should handle provider with undefined id in onDeleteFromProvider', async () => {
+ const mockDomain = createMockDomain();
+ const mockProvider = createMockIdentityProvider({ id: undefined });
+
+ const { result } = renderUseDomainTable(mockOptions);
+
+ await result.current.onDeleteFromProvider(mockDomain, mockProvider);
+
+ await waitFor(() => {
+ expect(result.current.isDeleting).toBe(false);
+ });
+
+ expect(
+ mockCoreClient.getMyOrganizationApiClient().organization.identityProviders.domains.delete,
+ ).toHaveBeenCalledWith(undefined, mockDomain.domain);
+ });
+
+ it('should call useTranslator with correct parameters', () => {
+ const useTranslatorSpy = vi.spyOn(useTranslatorModule, 'useTranslator');
+ renderUseDomainTable(mockOptions);
+
+ expect(useTranslatorSpy).toHaveBeenCalledWith(
+ 'domain_management.domain_table.notifications',
+ {},
+ );
+ });
+ });
+});
diff --git a/packages/react/src/hooks/my-organization/shared/services/use-domain-table-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-domain-table-service.ts
new file mode 100644
index 000000000..6c3023f65
--- /dev/null
+++ b/packages/react/src/hooks/my-organization/shared/services/use-domain-table-service.ts
@@ -0,0 +1,222 @@
+/**
+ * Internal domain table service hook.
+ * Handles data fetching and CRUD operations for domains.
+ * @module use-domain-table-service
+ * @internal
+ */
+
+import {
+ type Domain,
+ type IdpKnownResponse,
+ type CreateOrganizationDomainRequestContent,
+ type IdentityProviderAssociatedWithDomain,
+ BusinessError,
+} from '@auth0/universal-components-core';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { useCallback, useState } from 'react';
+
+import { useCoreClient } from '@/hooks/shared/use-core-client';
+import { useTranslator } from '@/hooks/shared/use-translator';
+import type {
+ UseDomainTableServiceOptions,
+ UseDomainTableServiceReturn,
+} from '@/types/my-organization/domain-management/domain-table-types';
+
+const domainQueryKeys = {
+ all: ['domains'] as const,
+ list: () => [...domainQueryKeys.all, 'list'] as const,
+ providers: (domainId: string) => [...domainQueryKeys.all, 'providers', domainId] as const,
+};
+
+/**
+ * Internal service hook for domain table data and CRUD operations.
+ * @param options - Service options including actions and custom messages.
+ * @returns Domain data, mutations, and actions.
+ * @internal
+ */
+export function useDomainTableService({
+ createAction,
+ deleteAction,
+ verifyAction,
+ associateToProviderAction,
+ deleteFromProviderAction,
+ customMessages,
+}: UseDomainTableServiceOptions): UseDomainTableServiceReturn {
+ const { t } = useTranslator('domain_management.domain_table.notifications', customMessages);
+ const { coreClient } = useCoreClient();
+ const queryClient = useQueryClient();
+
+ const [selectedDomainId, setSelectedDomainId] = useState(null);
+ const [selectedDomainName, setSelectedDomainName] = useState(null);
+
+ const fetchProvidersForDomain = async (domainName: string) => {
+ const api = coreClient!.getMyOrganizationApiClient();
+
+ const allProvidersResponse = await api.organization.identityProviders.list();
+ const allProviders = allProvidersResponse?.identity_providers ?? [];
+
+ return allProviders.map(
+ (provider): IdentityProviderAssociatedWithDomain => ({
+ ...provider,
+ is_associated: provider.domains?.includes(domainName) ?? false,
+ }),
+ );
+ };
+
+ const domainsQuery = useQuery({
+ queryKey: domainQueryKeys.list(),
+ queryFn: async () => {
+ const { response } = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.domains.list();
+ return response?.organization_domains ?? [];
+ },
+ enabled: !!coreClient,
+ });
+
+ const providersQuery = useQuery({
+ queryKey: domainQueryKeys.providers(selectedDomainId ?? ''),
+ queryFn: () => fetchProvidersForDomain(selectedDomainName!),
+ enabled: !!coreClient && !!selectedDomainId && !!selectedDomainName,
+ });
+
+ const createDomainMutation = useMutation({
+ mutationFn: async (data: CreateOrganizationDomainRequestContent): Promise => {
+ if (createAction?.onBefore && !createAction.onBefore(data as Domain)) {
+ throw new BusinessError({ message: t('domain_create.on_before') });
+ }
+ return coreClient!.getMyOrganizationApiClient().organization.domains.create(data);
+ },
+ onSuccess: (result) => {
+ createAction?.onAfter?.(result);
+ queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
+ },
+ });
+
+ const verifyDomainMutation = useMutation({
+ mutationFn: async (domain: Domain): Promise => {
+ if (verifyAction?.onBefore && !verifyAction.onBefore(domain)) {
+ throw new BusinessError({ message: t('domain_verify.on_before') });
+ }
+ const response = await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.domains.verify.create(domain.id);
+ return response.status === 'verified';
+ },
+ onSuccess: (_, domain) => {
+ verifyAction?.onAfter?.(domain);
+ queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
+ },
+ });
+
+ const deleteDomainMutation = useMutation({
+ mutationFn: async (domain: Domain): Promise => {
+ if (deleteAction?.onBefore && !deleteAction.onBefore(domain)) {
+ throw new BusinessError({ message: t('domain_delete.on_before') });
+ }
+ await coreClient!.getMyOrganizationApiClient().organization.domains.delete(domain.id);
+ },
+ onSuccess: (_, domain) => {
+ deleteAction?.onAfter?.(domain);
+ queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
+ queryClient.removeQueries({ queryKey: domainQueryKeys.providers(domain.id) });
+ },
+ });
+
+ const associateToProviderMutation = useMutation({
+ mutationFn: async ({ domain, provider }: { domain: Domain; provider: IdpKnownResponse }) => {
+ if (
+ associateToProviderAction?.onBefore &&
+ !associateToProviderAction.onBefore(domain, provider)
+ ) {
+ throw new BusinessError({ message: t('domain_associate_provider.on_before') });
+ }
+ await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.identityProviders.domains.create(provider.id!, { domain: domain.domain });
+ },
+ onSuccess: (_, { domain, provider }) => {
+ associateToProviderAction?.onAfter?.(domain, provider);
+ queryClient.invalidateQueries({ queryKey: domainQueryKeys.providers(domain.id) });
+ },
+ });
+
+ const deleteFromProviderMutation = useMutation({
+ mutationFn: async ({ domain, provider }: { domain: Domain; provider: IdpKnownResponse }) => {
+ if (
+ deleteFromProviderAction?.onBefore &&
+ !deleteFromProviderAction.onBefore(domain, provider)
+ ) {
+ throw new BusinessError({ message: t('domain_delete_provider.on_before') });
+ }
+ await coreClient!
+ .getMyOrganizationApiClient()
+ .organization.identityProviders.domains.delete(provider.id!, domain.domain);
+ },
+ onSuccess: (_, { domain, provider }) => {
+ deleteFromProviderAction?.onAfter?.(domain, provider);
+ queryClient.invalidateQueries({ queryKey: domainQueryKeys.providers(domain.id) });
+ },
+ });
+
+ const onCreateDomain = useCallback(
+ (data: CreateOrganizationDomainRequestContent) => createDomainMutation.mutateAsync(data),
+ [createDomainMutation],
+ );
+
+ const onVerifyDomain = useCallback(
+ (domain: Domain) => verifyDomainMutation.mutateAsync(domain),
+ [verifyDomainMutation],
+ );
+
+ const onDeleteDomain = useCallback(
+ (domain: Domain) => deleteDomainMutation.mutateAsync(domain),
+ [deleteDomainMutation],
+ );
+
+ const onAssociateToProvider = useCallback(
+ (domain: Domain, provider: IdpKnownResponse) =>
+ associateToProviderMutation.mutateAsync({ domain, provider }),
+ [associateToProviderMutation],
+ );
+
+ const onDeleteFromProvider = useCallback(
+ (domain: Domain, provider: IdpKnownResponse) =>
+ deleteFromProviderMutation.mutateAsync({ domain, provider }),
+ [deleteFromProviderMutation],
+ );
+
+ const fetchProviders = useCallback(
+ async (domain: Domain) => {
+ setSelectedDomainId(domain.id);
+ setSelectedDomainName(domain.domain);
+ await queryClient.ensureQueryData({
+ queryKey: domainQueryKeys.providers(domain.id),
+ queryFn: () => fetchProvidersForDomain(domain.domain),
+ });
+ },
+ [queryClient, coreClient],
+ );
+
+ const fetchDomains = useCallback(async () => {
+ await queryClient.getQueryData(domainQueryKeys.list());
+ }, [queryClient]);
+
+ return {
+ domains: domainsQuery.data ?? [],
+ providers: providersQuery.data ?? [],
+ isFetching: domainsQuery.isLoading,
+ isCreating: createDomainMutation.isPending,
+ isDeleting: deleteDomainMutation.isPending,
+ isVerifying: verifyDomainMutation.isPending,
+ isLoadingProviders: providersQuery.isLoading,
+
+ fetchProviders,
+ fetchDomains,
+ onCreateDomain,
+ onVerifyDomain,
+ onDeleteDomain,
+ onAssociateToProvider,
+ onDeleteFromProvider,
+ };
+}
diff --git a/packages/react/src/hooks/my-organization/use-domain-table-logic.ts b/packages/react/src/hooks/my-organization/use-domain-table-logic.ts
deleted file mode 100644
index 4d9ab05f8..000000000
--- a/packages/react/src/hooks/my-organization/use-domain-table-logic.ts
+++ /dev/null
@@ -1,257 +0,0 @@
-/**
- * Domain table UI logic hook.
- * @module use-domain-table-logic
- * @internal
- */
-
-import { type Domain, type IdpKnownResponse } from '@auth0/universal-components-core';
-import { useCallback, useEffect, useState } from 'react';
-
-import { showToast } from '@/components/auth0/shared/toast';
-import { useErrorHandler } from '@/hooks/shared/use-error-handler';
-import type {
- UseDomainTableLogicOptions,
- UseDomainTableLogicResult,
-} from '@/types/my-organization/domain-management/domain-table-types';
-
-/**
- * Hook for domain table modal state and action handlers.
- * @param props - Component props.
- * @param props.t - Translation function
- * @param props.onCreateDomain - The on create domain
- * @param props.onVerifyDomain - The on verify domain
- * @param props.onDeleteDomain - The on delete domain
- * @param props.onAssociateToProvider - The on associate to provider
- * @param props.onDeleteFromProvider - The on delete from provider
- * @param props.fetchProviders - The fetch providers
- * @param props.fetchDomains - The fetch domains
- * @internal
- * @returns Hook state and methods
- */
-export function useDomainTableLogic({
- t,
- onCreateDomain,
- onVerifyDomain,
- onDeleteDomain,
- onAssociateToProvider,
- onDeleteFromProvider,
- fetchProviders,
- fetchDomains,
-}: UseDomainTableLogicOptions): UseDomainTableLogicResult {
- const handleError = useErrorHandler();
- const [showCreateModal, setShowCreateModal] = useState(false);
- const [showConfigureModal, setShowConfigureModal] = useState(false);
- const [showVerifyModal, setShowVerifyModal] = useState(false);
- const [verifyError, setVerifyError] = useState(undefined);
- const [showDeleteModal, setShowDeleteModal] = useState(false);
- const [selectedDomain, setSelectedDomain] = useState(null);
-
- const handleCreate = useCallback(
- async (domainUrl: string) => {
- try {
- const newDomain = await onCreateDomain({ domain: domainUrl });
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_create.success', {
- domainName: newDomain?.domain,
- }),
- });
- setSelectedDomain(newDomain);
- setShowCreateModal(false);
- setShowVerifyModal(true);
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_create.error'),
- });
- }
- },
- [onCreateDomain, t, handleError],
- );
-
- const handleVerify = useCallback(
- async (domain: Domain) => {
- try {
- const isVerified = await onVerifyDomain(domain);
- if (isVerified) {
- setShowVerifyModal(false);
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_verify.success', {
- domainName: domain.domain,
- }),
- });
- } else {
- setVerifyError(
- t('domain_verify.modal.errors.verification_failed', { domainName: domain.domain }),
- );
- }
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_verify.error'),
- });
- }
- },
- [onVerifyDomain, t, handleError],
- );
-
- const handleDelete = useCallback(
- async (domain: Domain) => {
- try {
- await onDeleteDomain(domain);
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_delete.success', {
- domainName: domain.domain,
- }),
- });
- setShowDeleteModal(false);
- setShowVerifyModal(false);
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_delete.error'),
- });
- }
- },
- [onDeleteDomain, t, handleError],
- );
-
- const handleToggleSwitch = useCallback(
- async (domain: Domain, provider: IdpKnownResponse, newCheckedValue: boolean) => {
- if (newCheckedValue) {
- try {
- await onAssociateToProvider(domain, provider);
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_associate_provider.success', {
- domain: domain.domain,
- idp: provider.name,
- }),
- });
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_associate_provider.error'),
- });
- }
- } else {
- try {
- await onDeleteFromProvider(domain, provider);
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_delete_provider.success', {
- domain: domain.domain,
- idp: provider.name,
- }),
- });
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_delete_provider.error'),
- });
- }
- }
- },
- [onAssociateToProvider, onDeleteFromProvider, t, handleError],
- );
-
- const handleCloseVerifyModal = useCallback(() => {
- setShowVerifyModal(false);
- setVerifyError(undefined);
- }, []);
-
- const handleCreateClick = useCallback(() => {
- setShowCreateModal(true);
- }, []);
-
- const handleConfigureClick = useCallback(
- async (domain: Domain) => {
- setSelectedDomain(domain);
- if (domain.status !== 'verified') {
- setShowVerifyModal(true);
- } else {
- try {
- await fetchProviders(domain);
- setShowConfigureModal(true);
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.fetch_providers_error'),
- });
- }
- }
- },
- [fetchProviders, t, handleError],
- );
-
- const handleVerifyClick = useCallback(
- async (domain: Domain) => {
- setSelectedDomain(domain);
- try {
- const isVerified = await onVerifyDomain(domain);
- if (isVerified) {
- await fetchProviders(domain);
- setShowConfigureModal(true);
- showToast({
- type: 'success',
- message: t('domain_table.notifications.domain_verify.success', {
- domainName: domain.domain,
- }),
- });
- } else {
- showToast({
- type: 'error',
- message: t('domain_table.notifications.domain_verify.verification_failed', {
- domainName: domain.domain,
- }),
- });
- }
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.domain_verify.error'),
- });
- }
- },
- [onVerifyDomain, fetchProviders, t, handleError],
- );
-
- const handleDeleteClick = useCallback((domain: Domain) => {
- setSelectedDomain(domain);
- setShowVerifyModal(false);
- setShowDeleteModal(true);
- }, []);
-
- // Initialization
- useEffect(() => {
- try {
- fetchDomains();
- } catch (error) {
- handleError(error, {
- fallbackMessage: t('domain_table.notifications.fetch_domains_error'),
- });
- }
- }, []);
-
- return {
- // Modal state
- showCreateModal,
- showConfigureModal,
- showVerifyModal,
- showDeleteModal,
- verifyError,
- selectedDomain,
-
- // State setters
- setShowCreateModal,
- setShowConfigureModal,
- setShowVerifyModal,
- setShowDeleteModal,
-
- // Handlers
- handleCreate,
- handleVerify,
- handleDelete,
- handleToggleSwitch,
- handleCloseVerifyModal,
- handleCreateClick,
- handleConfigureClick,
- handleVerifyClick,
- handleDeleteClick,
- };
-}
diff --git a/packages/react/src/hooks/my-organization/use-domain-table.ts b/packages/react/src/hooks/my-organization/use-domain-table.ts
index 6da2f8402..afc9b2f6d 100644
--- a/packages/react/src/hooks/my-organization/use-domain-table.ts
+++ b/packages/react/src/hooks/my-organization/use-domain-table.ts
@@ -1,41 +1,26 @@
/**
- * Domain table data and mutations hook.
+ * Domain table hook.
+ * Single public hook combining data operations and UI logic.
* @module use-domain-table
*/
-import {
- type Domain,
- type IdpKnownResponse,
- type CreateOrganizationDomainRequestContent,
- type IdentityProviderAssociatedWithDomain,
- BusinessError,
-} from '@auth0/universal-components-core';
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { useState } from 'react';
-
-import { useCoreClient } from '@/hooks/shared/use-core-client';
+import { type Domain, type IdpKnownResponse } from '@auth0/universal-components-core';
+import { useCallback, useState } from 'react';
+
+import { showToast } from '@/components/auth0/shared/toast';
+import { useDomainTableService } from '@/hooks/my-organization/shared/services/use-domain-table-service';
+import { useErrorHandler } from '@/hooks/shared/use-error-handler';
import { useTranslator } from '@/hooks/shared/use-translator';
import type {
UseDomainTableOptions,
- UseDomainTableResult,
+ UseDomainTableReturn,
} from '@/types/my-organization/domain-management/domain-table-types';
-const domainQueryKeys = {
- all: ['domains'] as const,
- list: () => [...domainQueryKeys.all, 'list'] as const,
- providers: (domainId: string) => [...domainQueryKeys.all, 'providers', domainId] as const,
-};
-
/**
- * Hook for domain table data fetching and CRUD operations.
- * @param props - Component props.
- * @param props.createAction - Configuration for the create action
- * @param props.deleteAction - Configuration for the delete action
- * @param props.verifyAction - Configuration for the verify action
- * @param props.associateToProviderAction - Configuration for associating to a provider
- * @param props.deleteFromProviderAction - Configuration for deleting from a provider
- * @param props.customMessages - Custom translation messages to override defaults
- * @returns Hook state and methods
+ * Hook for domain table data, CRUD operations, and UI logic.
+ * Consumes the internal service hook and manages modal/UI state.
+ * @param options - Hook options including actions and custom messages.
+ * @returns Combined data, loading states, UI state, and handlers.
*/
export function useDomainTable({
createAction,
@@ -44,149 +29,246 @@ export function useDomainTable({
associateToProviderAction,
deleteFromProviderAction,
customMessages,
-}: UseDomainTableOptions): UseDomainTableResult {
- const { t } = useTranslator('domain_management.domain_table.notifications', customMessages);
- const { coreClient } = useCoreClient();
- const queryClient = useQueryClient();
-
- const [selectedDomainId, setSelectedDomainId] = useState(null);
- const [selectedDomainName, setSelectedDomainName] = useState(null);
-
- const fetchProvidersForDomain = async (domainName: string) => {
- const api = coreClient!.getMyOrganizationApiClient();
-
- const allProvidersResponse = await api.organization.identityProviders.list();
- const allProviders = allProvidersResponse?.identity_providers ?? [];
-
- return allProviders.map(
- (provider): IdentityProviderAssociatedWithDomain => ({
- ...provider,
- is_associated: provider.domains?.includes(domainName) ?? false,
- }),
- );
- };
+}: UseDomainTableOptions): UseDomainTableReturn {
+ const { t } = useTranslator('domain_management', customMessages);
+ const handleError = useErrorHandler();
- const domainsQuery = useQuery({
- queryKey: domainQueryKeys.list(),
- queryFn: async () => {
- const { response } = await coreClient!
- .getMyOrganizationApiClient()
- .organization.domains.list();
- return response?.organization_domains ?? [];
- },
- enabled: !!coreClient,
+ const {
+ domains,
+ providers,
+ isFetching,
+ isCreating,
+ isDeleting,
+ isVerifying,
+ isLoadingProviders,
+ fetchProviders,
+ onCreateDomain,
+ onVerifyDomain,
+ onDeleteDomain,
+ onAssociateToProvider,
+ onDeleteFromProvider,
+ } = useDomainTableService({
+ createAction,
+ deleteAction,
+ verifyAction,
+ associateToProviderAction,
+ deleteFromProviderAction,
+ customMessages,
});
- const providersQuery = useQuery({
- queryKey: domainQueryKeys.providers(selectedDomainId ?? ''),
- queryFn: () => fetchProvidersForDomain(selectedDomainName!),
- enabled: !!coreClient && !!selectedDomainId && !!selectedDomainName,
- });
+ const [showCreateModal, setShowCreateModal] = useState(false);
+ const [showConfigureModal, setShowConfigureModal] = useState(false);
+ const [showVerifyModal, setShowVerifyModal] = useState(false);
+ const [verifyError, setVerifyError] = useState(undefined);
+ const [showDeleteModal, setShowDeleteModal] = useState(false);
+ const [selectedDomain, setSelectedDomain] = useState(null);
- const createDomainMutation = useMutation({
- mutationFn: async (data: CreateOrganizationDomainRequestContent): Promise => {
- if (createAction?.onBefore && !createAction.onBefore(data as Domain)) {
- throw new BusinessError({ message: t('domain_create.on_before') });
+ const handleCreate = useCallback(
+ async (domainUrl: string) => {
+ try {
+ const newDomain = await onCreateDomain({ domain: domainUrl });
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_create.success', {
+ domainName: newDomain?.domain,
+ }),
+ });
+ setSelectedDomain(newDomain);
+ setShowCreateModal(false);
+ setShowVerifyModal(true);
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_create.error'),
+ });
}
- return coreClient!.getMyOrganizationApiClient().organization.domains.create(data);
- },
- onSuccess: (result) => {
- createAction?.onAfter?.(result);
- queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
},
- });
+ [onCreateDomain, t, handleError],
+ );
- const verifyDomainMutation = useMutation({
- mutationFn: async (domain: Domain): Promise => {
- if (verifyAction?.onBefore && !verifyAction.onBefore(domain)) {
- throw new BusinessError({ message: t('domain_verify.on_before') });
+ const handleVerify = useCallback(
+ async (domain: Domain) => {
+ try {
+ const isVerified = await onVerifyDomain(domain);
+ if (isVerified) {
+ setShowVerifyModal(false);
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_verify.success', {
+ domainName: domain.domain,
+ }),
+ });
+ } else {
+ setVerifyError(
+ t('domain_verify.modal.errors.verification_failed', { domainName: domain.domain }),
+ );
+ }
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_verify.error'),
+ });
}
- const response = await coreClient!
- .getMyOrganizationApiClient()
- .organization.domains.verify.create(domain.id);
- return response.status === 'verified';
},
- onSuccess: (_, domain) => {
- verifyAction?.onAfter?.(domain);
- queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
- },
- });
+ [onVerifyDomain, t, handleError],
+ );
- const deleteDomainMutation = useMutation({
- mutationFn: async (domain: Domain): Promise => {
- if (deleteAction?.onBefore && !deleteAction.onBefore(domain)) {
- throw new BusinessError({ message: t('domain_delete.on_before') });
+ const handleDelete = useCallback(
+ async (domain: Domain) => {
+ try {
+ await onDeleteDomain(domain);
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_delete.success', {
+ domainName: domain.domain,
+ }),
+ });
+ setShowDeleteModal(false);
+ setShowVerifyModal(false);
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_delete.error'),
+ });
}
- await coreClient!.getMyOrganizationApiClient().organization.domains.delete(domain.id);
- },
- onSuccess: (_, domain) => {
- deleteAction?.onAfter?.(domain);
- queryClient.invalidateQueries({ queryKey: domainQueryKeys.list() });
- queryClient.removeQueries({ queryKey: domainQueryKeys.providers(domain.id) });
},
- });
+ [onDeleteDomain, t, handleError],
+ );
- const associateToProviderMutation = useMutation({
- mutationFn: async ({ domain, provider }: { domain: Domain; provider: IdpKnownResponse }) => {
- if (
- associateToProviderAction?.onBefore &&
- !associateToProviderAction.onBefore(domain, provider)
- ) {
- throw new BusinessError({ message: t('domain_associate_provider.on_before') });
+ const handleToggleSwitch = useCallback(
+ async (domain: Domain, provider: IdpKnownResponse, newCheckedValue: boolean) => {
+ if (newCheckedValue) {
+ try {
+ await onAssociateToProvider(domain, provider);
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_associate_provider.success', {
+ domain: domain.domain,
+ idp: provider.name,
+ }),
+ });
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_associate_provider.error'),
+ });
+ }
+ } else {
+ try {
+ await onDeleteFromProvider(domain, provider);
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_delete_provider.success', {
+ domain: domain.domain,
+ idp: provider.name,
+ }),
+ });
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_delete_provider.error'),
+ });
+ }
}
- await coreClient!
- .getMyOrganizationApiClient()
- .organization.identityProviders.domains.create(provider.id!, { domain: domain.domain });
- },
- onSuccess: (_, { domain, provider }) => {
- associateToProviderAction?.onAfter?.(domain, provider);
- queryClient.invalidateQueries({ queryKey: domainQueryKeys.providers(domain.id) });
},
- });
+ [onAssociateToProvider, onDeleteFromProvider, t, handleError],
+ );
- const deleteFromProviderMutation = useMutation({
- mutationFn: async ({ domain, provider }: { domain: Domain; provider: IdpKnownResponse }) => {
- if (
- deleteFromProviderAction?.onBefore &&
- !deleteFromProviderAction.onBefore(domain, provider)
- ) {
- throw new BusinessError({ message: t('domain_delete_provider.on_before') });
+ const handleCloseVerifyModal = useCallback(() => {
+ setShowVerifyModal(false);
+ setVerifyError(undefined);
+ }, []);
+
+ const handleCreateClick = useCallback(() => {
+ setShowCreateModal(true);
+ }, []);
+
+ const handleConfigureClick = useCallback(
+ async (domain: Domain) => {
+ setSelectedDomain(domain);
+ if (domain.status !== 'verified') {
+ setShowVerifyModal(true);
+ } else {
+ try {
+ await fetchProviders(domain);
+ setShowConfigureModal(true);
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.fetch_providers_error'),
+ });
+ }
}
- await coreClient!
- .getMyOrganizationApiClient()
- .organization.identityProviders.domains.delete(provider.id!, domain.domain);
},
- onSuccess: (_, { domain, provider }) => {
- deleteFromProviderAction?.onAfter?.(domain, provider);
- queryClient.invalidateQueries({ queryKey: domainQueryKeys.providers(domain.id) });
+ [fetchProviders, t, handleError],
+ );
+
+ const handleVerifyClick = useCallback(
+ async (domain: Domain) => {
+ setSelectedDomain(domain);
+ try {
+ const isVerified = await onVerifyDomain(domain);
+ if (isVerified) {
+ await fetchProviders(domain);
+ setShowConfigureModal(true);
+ showToast({
+ type: 'success',
+ message: t('domain_table.notifications.domain_verify.success', {
+ domainName: domain.domain,
+ }),
+ });
+ } else {
+ showToast({
+ type: 'error',
+ message: t('domain_table.notifications.domain_verify.verification_failed', {
+ domainName: domain.domain,
+ }),
+ });
+ }
+ } catch (error) {
+ handleError(error, {
+ fallbackMessage: t('domain_table.notifications.domain_verify.error'),
+ });
+ }
},
- });
+ [onVerifyDomain, fetchProviders, t, handleError],
+ );
+
+ const handleDeleteClick = useCallback((domain: Domain) => {
+ setSelectedDomain(domain);
+ setShowVerifyModal(false);
+ setShowDeleteModal(true);
+ }, []);
return {
- domains: domainsQuery.data ?? [],
- providers: providersQuery.data ?? [],
- isFetching: domainsQuery.isLoading,
- isCreating: createDomainMutation.isPending,
- isDeleting: deleteDomainMutation.isPending,
- isVerifying: verifyDomainMutation.isPending,
- isLoadingProviders: providersQuery.isLoading,
- fetchProviders: async (domain: Domain) => {
- setSelectedDomainId(domain.id);
- setSelectedDomainName(domain.domain);
- await queryClient.ensureQueryData({
- queryKey: domainQueryKeys.providers(domain.id),
- queryFn: () => fetchProvidersForDomain(domain.domain),
- });
- },
- fetchDomains: async () => {
- await queryClient.getQueryData(domainQueryKeys.list());
- },
- onCreateDomain: (data) => createDomainMutation.mutateAsync(data),
- onVerifyDomain: (domain) => verifyDomainMutation.mutateAsync(domain),
- onDeleteDomain: (domain) => deleteDomainMutation.mutateAsync(domain),
- onAssociateToProvider: (domain, provider) =>
- associateToProviderMutation.mutateAsync({ domain, provider }),
- onDeleteFromProvider: (domain, provider) =>
- deleteFromProviderMutation.mutateAsync({ domain, provider }),
+ // Data
+ domains,
+ providers,
+
+ // Loading states
+ isFetching,
+ isCreating,
+ isDeleting,
+ isVerifying,
+ isLoadingProviders,
+
+ // Modal state
+ showCreateModal,
+ showConfigureModal,
+ showVerifyModal,
+ showDeleteModal,
+ verifyError,
+ selectedDomain,
+
+ // State setters
+ setShowCreateModal,
+ setShowConfigureModal,
+ setShowVerifyModal,
+ setShowDeleteModal,
+
+ // Handlers
+ handleCreate,
+ handleVerify,
+ handleDelete,
+ handleToggleSwitch,
+ handleCloseVerifyModal,
+ handleCreateClick,
+ handleConfigureClick,
+ handleVerifyClick,
+ handleDeleteClick,
};
}
diff --git a/packages/react/src/tests/utils/__mocks__/my-organization/domain-management/domain.mocks.ts b/packages/react/src/tests/utils/__mocks__/my-organization/domain-management/domain.mocks.ts
index d4baa2733..137512983 100644
--- a/packages/react/src/tests/utils/__mocks__/my-organization/domain-management/domain.mocks.ts
+++ b/packages/react/src/tests/utils/__mocks__/my-organization/domain-management/domain.mocks.ts
@@ -8,8 +8,9 @@ import { vi } from 'vitest';
import type {
DomainTableProps,
- UseDomainTableLogicOptions,
- UseDomainTableResult,
+ UseDomainTableReturn,
+ UseDomainTableServiceOptions,
+ UseDomainTableServiceReturn,
} from '@/types/my-organization/domain-management/domain-table-types';
export const createMockDomain = (overrides?: Partial): Domain => ({
@@ -68,7 +69,6 @@ export const createMockIdentityProviderAssociatedWithDomain = (
export const createMockIdentityProviderWithoutProvisioning = (
overrides: Partial = {},
): IdpKnownResponse => {
- // Use a strategy that doesn't have provisioning enabled by default
const baseProvider = {
id: 'con_abc123xyz456',
name: 'mock-provider-no-provisioning',
@@ -119,9 +119,9 @@ export const createMockDeleteAction = (): ComponentAction => ({
onAfter: vi.fn(),
});
-export const createMockLogic = (
- overrides: Partial = {},
-) => ({
+export const createMockDomainTableReturn = (
+ overrides: Partial = {},
+): UseDomainTableReturn => ({
domains: [createMockDomain(), createMockVerifiedDomain()],
providers: [],
isCreating: false,
@@ -129,25 +129,6 @@ export const createMockLogic = (
isFetching: false,
isLoadingProviders: false,
isDeleting: false,
- schema: undefined,
- styling: { variables: { common: {}, light: {}, dark: {} }, classes: {} },
- hideHeader: false,
- readOnly: false,
- customMessages: {},
- createAction: undefined,
- onOpenProvider: undefined,
- onCreateProvider: undefined,
- fetchProviders: vi.fn(),
- fetchDomains: vi.fn(),
- onCreateDomain: vi.fn(),
- onVerifyDomain: vi.fn(),
- onDeleteDomain: vi.fn(),
- onAssociateToProvider: vi.fn(),
- onDeleteFromProvider: vi.fn(),
- ...overrides,
-});
-
-export const createMockApi = (overrides: Partial = {}) => ({
showCreateModal: false,
showConfigureModal: false,
showVerifyModal: false,
@@ -156,8 +137,8 @@ export const createMockApi = (overrides: Partial = {
selectedDomain: null,
setShowCreateModal: vi.fn(),
setShowConfigureModal: vi.fn(),
- setShowDeleteModal: vi.fn(),
setShowVerifyModal: vi.fn(),
+ setShowDeleteModal: vi.fn(),
handleCreate: vi.fn(),
handleVerify: vi.fn(),
handleDelete: vi.fn(),
@@ -169,3 +150,50 @@ export const createMockApi = (overrides: Partial = {
handleDeleteClick: vi.fn(),
...overrides,
});
+
+export const createMockDomainTableServiceOptions = (
+ overrides?: Partial,
+): UseDomainTableServiceOptions => ({
+ createAction: {
+ onBefore: vi.fn().mockReturnValue(true),
+ onAfter: vi.fn(),
+ },
+ deleteAction: {
+ onBefore: vi.fn().mockReturnValue(true),
+ onAfter: vi.fn(),
+ },
+ verifyAction: {
+ onBefore: vi.fn().mockReturnValue(true),
+ onAfter: vi.fn(),
+ },
+ associateToProviderAction: {
+ onBefore: vi.fn().mockReturnValue(true),
+ onAfter: vi.fn(),
+ },
+ deleteFromProviderAction: {
+ onBefore: vi.fn().mockReturnValue(true),
+ onAfter: vi.fn(),
+ },
+ customMessages: {},
+ ...overrides,
+});
+
+export const createMockDomainTableServiceReturn = (
+ overrides: Partial = {},
+): UseDomainTableServiceReturn => ({
+ domains: [],
+ providers: [],
+ isFetching: false,
+ isCreating: false,
+ isDeleting: false,
+ isVerifying: false,
+ isLoadingProviders: false,
+ fetchProviders: vi.fn(),
+ fetchDomains: vi.fn(),
+ onCreateDomain: vi.fn(),
+ onVerifyDomain: vi.fn(),
+ onDeleteDomain: vi.fn(),
+ onAssociateToProvider: vi.fn(),
+ onDeleteFromProvider: vi.fn(),
+ ...overrides,
+});
diff --git a/packages/react/src/types/my-organization/domain-management/domain-table-types.ts b/packages/react/src/types/my-organization/domain-management/domain-table-types.ts
index e7aa84bb4..e4da2d010 100644
--- a/packages/react/src/types/my-organization/domain-management/domain-table-types.ts
+++ b/packages/react/src/types/my-organization/domain-management/domain-table-types.ts
@@ -15,9 +15,9 @@ import type {
DomainVerifyMessages,
DomainTableMessages,
CreateOrganizationDomainRequestContent,
- EnhancedTranslationFunction,
IdentityProviderAssociatedWithDomain,
} from '@auth0/universal-components-core';
+import type React from 'react';
export type { Domain };
@@ -56,12 +56,6 @@ export interface DomainTableProps
onCreateProvider?: () => void;
}
-// DomainTableView component props
-export interface DomainTableViewProps {
- logic: UseDomainTableResult & DomainTableProps;
- handlers: UseDomainTableLogicResult;
-}
-
/** Props for DomainTable actions column. */
export interface DomainTableActionsColumnProps {
customMessages?: Partial;
@@ -73,6 +67,7 @@ export interface DomainTableActionsColumnProps {
onDelete: (domain: Domain) => void;
}
+/** Options for domain table hooks (shared by service and public hook). */
export interface UseDomainTableOptions {
createAction?: DomainTableProps['createAction'];
verifyAction?: DomainTableProps['verifyAction'];
@@ -82,7 +77,11 @@ export interface UseDomainTableOptions {
customMessages?: DomainTableProps['customMessages'];
}
-export interface UseDomainTableResult extends SharedComponentProps {
+/** @internal */
+export type UseDomainTableServiceOptions = UseDomainTableOptions;
+
+/** Return type for the internal domain table service hook. */
+export interface UseDomainTableServiceReturn {
domains: Domain[];
providers: IdentityProviderAssociatedWithDomain[];
isFetching: boolean;
@@ -92,25 +91,26 @@ export interface UseDomainTableResult extends SharedComponentProps {
isVerifying: boolean;
fetchProviders: (domain: Domain) => Promise;
fetchDomains: () => Promise;
- onCreateDomain: (data: CreateOrganizationDomainRequestContent) => Promise;
- onVerifyDomain: (data: Domain) => Promise;
+ onCreateDomain: (data: CreateOrganizationDomainRequestContent) => Promise;
+ onVerifyDomain: (domain: Domain) => Promise;
onDeleteDomain: (domain: Domain) => Promise;
onAssociateToProvider: (domain: Domain, provider: IdpKnownResponse) => Promise;
onDeleteFromProvider: (domain: Domain, provider: IdpKnownResponse) => Promise;
}
-export interface UseDomainTableLogicOptions {
- t: EnhancedTranslationFunction;
- onCreateDomain: UseDomainTableResult['onCreateDomain'];
- onVerifyDomain: UseDomainTableResult['onVerifyDomain'];
- onDeleteDomain: UseDomainTableResult['onDeleteDomain'];
- onAssociateToProvider: UseDomainTableResult['onAssociateToProvider'];
- onDeleteFromProvider: UseDomainTableResult['onDeleteFromProvider'];
- fetchProviders: UseDomainTableResult['fetchProviders'];
- fetchDomains: UseDomainTableResult['fetchDomains'];
-}
+/** Return type for the public domain table hook. */
+export interface UseDomainTableReturn {
+ // Data
+ domains: Domain[];
+ providers: IdentityProviderAssociatedWithDomain[];
+
+ // Loading states
+ isFetching: boolean;
+ isCreating: boolean;
+ isDeleting: boolean;
+ isVerifying: boolean;
+ isLoadingProviders: boolean;
-export interface UseDomainTableLogicResult {
// Modal state
showCreateModal: boolean;
showConfigureModal: boolean;
@@ -120,19 +120,36 @@ export interface UseDomainTableLogicResult {
selectedDomain: Domain | null;
// State setters
- setShowCreateModal: (show: boolean) => void;
- setShowConfigureModal: (show: boolean) => void;
- setShowVerifyModal: (show: boolean) => void;
- setShowDeleteModal: (show: boolean) => void;
+ setShowCreateModal: React.Dispatch>;
+ setShowConfigureModal: React.Dispatch>;
+ setShowVerifyModal: React.Dispatch>;
+ setShowDeleteModal: React.Dispatch>;
// Handlers
handleCreate: (domainUrl: string) => Promise;
handleVerify: (domain: Domain) => Promise;
- handleDelete: (domain: Domain) => void;
- handleToggleSwitch: (domain: Domain, provider: IdpKnownResponse, checked: boolean) => void;
+ handleDelete: (domain: Domain) => Promise;
+ handleToggleSwitch: (
+ domain: Domain,
+ provider: IdpKnownResponse,
+ checked: boolean,
+ ) => Promise;
handleCloseVerifyModal: () => void;
handleCreateClick: () => void;
handleConfigureClick: (domain: Domain) => void;
handleVerifyClick: (domain: Domain) => Promise;
handleDeleteClick: (domain: Domain) => void;
}
+
+/** Props for the DomainTableView presentational component. @internal */
+export interface DomainTableViewProps {
+ domainTable: UseDomainTableReturn;
+ schema: DomainTableProps['schema'];
+ styling: DomainTableProps['styling'];
+ hideHeader: DomainTableProps['hideHeader'];
+ readOnly: DomainTableProps['readOnly'];
+ customMessages: DomainTableProps['customMessages'];
+ createAction: DomainTableProps['createAction'];
+ onOpenProvider: DomainTableProps['onOpenProvider'];
+ onCreateProvider: DomainTableProps['onCreateProvider'];
+}