From 250db634bd3becde5b71feaf6f7b110546543d59 Mon Sep 17 00:00:00 2001 From: harish-sundar_akto Date: Thu, 28 May 2026 23:54:54 +0530 Subject: [PATCH] refactor(react): sso-provider-create hook service separation --- .../my-organization/sso-provider-create.tsx | 11 +- .../use-sso-provider-create-logic.test.ts | 29 ++- .../use-sso-provider-create-service.test.ts} | 38 ++-- .../use-sso-provider-create-service.ts | 151 +++++++++++++ .../use-sso-provider-create-logic.ts | 85 -------- .../use-sso-provider-create.ts | 201 +++++++----------- .../sso-provider/sso-provider-create-types.ts | 9 +- 7 files changed, 266 insertions(+), 258 deletions(-) rename packages/react/src/hooks/my-organization/{__tests__/use-sso-provider-create.test.ts => shared/__tests__/use-sso-provider-create-service.test.ts} (90%) create mode 100644 packages/react/src/hooks/my-organization/shared/services/use-sso-provider-create-service.ts delete mode 100644 packages/react/src/hooks/my-organization/use-sso-provider-create-logic.ts diff --git a/packages/react/src/components/auth0/my-organization/sso-provider-create.tsx b/packages/react/src/components/auth0/my-organization/sso-provider-create.tsx index 3ede9ed0e..98341f06e 100644 --- a/packages/react/src/components/auth0/my-organization/sso-provider-create.tsx +++ b/packages/react/src/components/auth0/my-organization/sso-provider-create.tsx @@ -12,7 +12,6 @@ import { StyledScope } from '@/components/auth0/shared/styled-scope'; import { Wizard } from '@/components/auth0/shared/wizard'; import type { StepProps } from '@/components/auth0/shared/wizard'; import { useSsoProviderCreate } from '@/hooks/my-organization/use-sso-provider-create'; -import { useSsoProviderCreateLogic } from '@/hooks/my-organization/use-sso-provider-create-logic'; import { useTheme } from '@/hooks/shared/use-theme'; import { useTranslator } from '@/hooks/shared/use-translator'; import type { @@ -48,10 +47,6 @@ function SsoProviderCreate(props: SsoProviderCreateProps) { onPrevious, } = props; - const { createProvider, isCreating } = useSsoProviderCreate({ - createAction, - customMessages, - }); const { formData, detailsRef, @@ -59,14 +54,16 @@ function SsoProviderCreate(props: SsoProviderCreateProps) { setFormData, handleCreate, createStepActions, + isCreating, isLoadingConfig, filteredStrategies, isLoadingIdpConfig, idpConfig, - } = useSsoProviderCreateLogic({ + } = useSsoProviderCreate({ + createAction, + customMessages, onNext, onPrevious, - createProvider, }); const { strategy, details, configure } = formData; diff --git a/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create-logic.test.ts b/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create-logic.test.ts index 0dfee80f8..a114d4e70 100644 --- a/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create-logic.test.ts +++ b/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create-logic.test.ts @@ -1,9 +1,8 @@ import { renderHook, act } from '@testing-library/react'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { useSsoProviderCreateLogic } from '../use-sso-provider-create-logic'; +import { useSsoProviderCreate } from '../use-sso-provider-create'; -// Mock useConfig and useIdpConfig to avoid hitting queryClient or network vi.mock('@/hooks/my-organization/use-config', () => ({ useConfig: () => ({ isLoadingConfig: false, @@ -16,23 +15,27 @@ vi.mock('@/hooks/my-organization/use-idp-config', () => ({ idpConfig: {}, }), })); +vi.mock('@/hooks/my-organization/shared/services/use-sso-provider-create-service', () => ({ + useSsoProviderCreateService: () => ({ + createProvider: mockCreateProvider, + isCreating: false, + }), +})); -// Minimal local mocks const mockCreateProvider = vi.fn(); const mockOnNext = vi.fn(); const mockOnPrevious = vi.fn(); -describe('useSsoProviderCreateLogic', () => { +describe('useSsoProviderCreate - logic behavior', () => { beforeEach(() => { vi.clearAllMocks(); }); it('should initialize formData and refs', () => { const { result } = renderHook(() => - useSsoProviderCreateLogic({ + useSsoProviderCreate({ onNext: mockOnNext, onPrevious: mockOnPrevious, - createProvider: mockCreateProvider, }), ); expect(result.current.formData).toEqual({}); @@ -42,10 +45,9 @@ describe('useSsoProviderCreateLogic', () => { it('should update formData via setFormData', () => { const { result } = renderHook(() => - useSsoProviderCreateLogic({ + useSsoProviderCreate({ onNext: mockOnNext, onPrevious: mockOnPrevious, - createProvider: mockCreateProvider, }), ); act(() => { @@ -63,10 +65,9 @@ describe('useSsoProviderCreateLogic', () => { it('should call createProvider with merged data on handleCreate', async () => { const { result } = renderHook(() => - useSsoProviderCreateLogic({ + useSsoProviderCreate({ onNext: mockOnNext, onPrevious: mockOnPrevious, - createProvider: mockCreateProvider, }), ); act(() => { @@ -75,7 +76,6 @@ describe('useSsoProviderCreateLogic', () => { details: { name: 'test', display_name: 'test provider' }, }); }); - // Mock configureRef.current.getData result.current.configureRef.current = { validate: vi.fn().mockResolvedValue(true), getData: vi @@ -94,13 +94,11 @@ describe('useSsoProviderCreateLogic', () => { it('createStepActions calls onNext and onPrevious handlers', async () => { const { result } = renderHook(() => - useSsoProviderCreateLogic({ + useSsoProviderCreate({ onNext: mockOnNext, onPrevious: mockOnPrevious, - createProvider: mockCreateProvider, }), ); - // Mock ref with validate and getData const ref = { current: { validate: vi.fn().mockResolvedValue(true), @@ -126,10 +124,9 @@ describe('useSsoProviderCreateLogic', () => { it('createStepActions returns false if validation fails', async () => { const { result } = renderHook(() => - useSsoProviderCreateLogic({ + useSsoProviderCreate({ onNext: mockOnNext, onPrevious: mockOnPrevious, - createProvider: mockCreateProvider, }), ); const ref = { diff --git a/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create.test.ts b/packages/react/src/hooks/my-organization/shared/__tests__/use-sso-provider-create-service.test.ts similarity index 90% rename from packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create.test.ts rename to packages/react/src/hooks/my-organization/shared/__tests__/use-sso-provider-create-service.test.ts index 6ddc04734..68a0d1f19 100644 --- a/packages/react/src/hooks/my-organization/__tests__/use-sso-provider-create.test.ts +++ b/packages/react/src/hooks/my-organization/shared/__tests__/use-sso-provider-create-service.test.ts @@ -6,7 +6,7 @@ import { renderHook, waitFor } from '@testing-library/react'; import { describe, expect, it, vi, beforeEach, type Mock } from 'vitest'; import { showToast } from '@/components/auth0/shared/toast'; -import { useSsoProviderCreate } from '@/hooks/my-organization/use-sso-provider-create'; +import { useSsoProviderCreateService } from '@/hooks/my-organization/shared/services/use-sso-provider-create-service'; import { useCoreClient } from '@/hooks/shared/use-core-client'; import { useErrorHandler } from '@/hooks/shared/use-error-handler'; import { useTranslator } from '@/hooks/shared/use-translator'; @@ -17,7 +17,7 @@ vi.mock('@/hooks/shared/use-translator'); vi.mock('@/components/auth0/shared/toast'); vi.mock('@/hooks/shared/use-error-handler'); -describe('useSsoProviderCreate', () => { +describe('useSsoProviderCreateService', () => { const mockCreate = vi.fn(); let mockHandleError: Mock; @@ -65,13 +65,15 @@ describe('useSsoProviderCreate', () => { (useErrorHandler as Mock).mockReturnValue(mockHandleError); }); - const renderUseSsoProviderCreate = (...args: Parameters) => { + const renderUseSsoProviderCreateService = ( + ...args: Parameters + ) => { const { wrapper } = createTestQueryClientWrapper(); - return renderHook(() => useSsoProviderCreate(...args), { wrapper }); + return renderHook(() => useSsoProviderCreateService(...args), { wrapper }); }; it('should initialize with isCreating as false', () => { - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); expect(result.current.isCreating).toBe(false); expect(typeof result.current.createProvider).toBe('function'); @@ -87,7 +89,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockResolvedValue(mockIdentityProvider); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(mockProviderData)).resolves.toBeUndefined(); @@ -113,7 +115,7 @@ describe('useSsoProviderCreate', () => { () => new Promise((resolve) => setTimeout(() => resolve(mockIdentityProvider), 100)), ); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); const createPromise = result.current.createProvider(mockProviderData); @@ -145,7 +147,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(error); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(mockProviderData)).rejects.toBeDefined(); @@ -178,7 +180,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(error); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(baseOktaProviderData)).rejects.toBeDefined(); @@ -201,7 +203,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(error); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(baseOktaProviderData)).rejects.toBeDefined(); @@ -223,7 +225,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(error); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(baseOktaProviderData)).rejects.toBeDefined(); @@ -243,7 +245,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(error); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(baseOktaProviderData)).rejects.toBeDefined(); @@ -265,7 +267,7 @@ describe('useSsoProviderCreate', () => { mockCreate.mockRejectedValue(new Error('Network error')); - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(mockProviderData)).rejects.toBeDefined(); @@ -288,7 +290,7 @@ describe('useSsoProviderCreate', () => { const onBefore = vi.fn().mockReturnValue(true); mockCreate.mockResolvedValue(mockIdentityProvider); - const { result } = renderUseSsoProviderCreate({ + const { result } = renderUseSsoProviderCreateService({ createAction: { onBefore }, }); @@ -310,7 +312,7 @@ describe('useSsoProviderCreate', () => { const onBefore = vi.fn().mockReturnValue(false); - const { result } = renderUseSsoProviderCreate({ + const { result } = renderUseSsoProviderCreateService({ createAction: { onBefore }, }); @@ -334,7 +336,7 @@ describe('useSsoProviderCreate', () => { const onAfter = vi.fn(); mockCreate.mockResolvedValue(mockIdentityProvider); - const { result } = renderUseSsoProviderCreate({ + const { result } = renderUseSsoProviderCreateService({ createAction: { onAfter }, }); @@ -356,7 +358,7 @@ describe('useSsoProviderCreate', () => { const onAfter = vi.fn(); mockCreate.mockRejectedValue(new Error('Creation failed')); - const { result } = renderUseSsoProviderCreate({ + const { result } = renderUseSsoProviderCreateService({ createAction: { onAfter }, }); @@ -377,7 +379,7 @@ describe('useSsoProviderCreate', () => { signingCert: 'cert123', }; - const { result } = renderUseSsoProviderCreate(); + const { result } = renderUseSsoProviderCreateService(); await expect(result.current.createProvider(mockProviderData)).resolves.toBeUndefined(); diff --git a/packages/react/src/hooks/my-organization/shared/services/use-sso-provider-create-service.ts b/packages/react/src/hooks/my-organization/shared/services/use-sso-provider-create-service.ts new file mode 100644 index 000000000..bfd03b656 --- /dev/null +++ b/packages/react/src/hooks/my-organization/shared/services/use-sso-provider-create-service.ts @@ -0,0 +1,151 @@ +/** + * Internal SSO provider creation service hook. + * Handles API interaction and mutation logic for creating SSO providers. + * @module use-sso-provider-create-service + * @internal + */ + +import { + hasApiErrorBody, + SsoProviderMappers, + type CreateIdentityProviderRequestContent, + type CreateIdentityProviderRequestContentPrivate, + type IdpKnownResponse, +} from '@auth0/universal-components-core'; +import { ssoProviderQueryKeys } from '@auth0/universal-components-core'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useCallback } from 'react'; + +import { showToast } from '@/components/auth0/shared/toast'; +import { useCoreClient } from '@/hooks/shared/use-core-client'; +import { useErrorHandler } from '@/hooks/shared/use-error-handler'; +import { useTranslator } from '@/hooks/shared/use-translator'; +import type { UseSsoProviderCreateOptions } from '@/types/my-organization/idp-management/sso-provider/sso-provider-create-types'; + +/** + * Extracts domain from discovery error detail. + * @param detail - Error detail string. + * @returns Domain string or null. + * @internal + */ +function extractDomainFromDiscoveryError(detail?: string): string | null { + if (!detail) return null; + const match = detail.match(/discovery failure:\s*(.+)/i); + return match?.[1]?.trim() ?? null; +} + +export interface UseSsoProviderCreateServiceReturn { + createProvider: (data: CreateIdentityProviderRequestContentPrivate) => Promise; + isCreating: boolean; +} + +/** + * Internal service hook for SSO provider creation API operations. + * @param options - Hook options. + * @param options.createAction - Callback after successful creation. + * @param options.customMessages - Custom translation messages. + * @returns Service state and methods. + * @internal + */ +export function useSsoProviderCreateService({ + createAction, + customMessages = {}, +}: UseSsoProviderCreateOptions = {}): UseSsoProviderCreateServiceReturn { + const { coreClient } = useCoreClient(); + const { t } = useTranslator('idp_management.create_sso_provider', customMessages); + const queryClient = useQueryClient(); + const handleError = useErrorHandler(); + const createProviderMutation = useMutation({ + mutationFn: async ( + data: CreateIdentityProviderRequestContentPrivate, + ): Promise => { + if (!coreClient) { + throw new Error('Core client not available'); + } + + const { strategy, name, display_name, ...configOptions } = data; + + const formData = { + strategy, + name, + display_name, + options: configOptions, + }; + + const apiRequestData: CreateIdentityProviderRequestContent = + SsoProviderMappers.createToAPI(formData); + + const result: IdpKnownResponse = await coreClient + .getMyOrganizationApiClient() + .organization.identityProviders.create(apiRequestData); + + return result; + }, + onSuccess: (result, data) => { + showToast({ + type: 'success', + message: t('notifications.provider_create_success', { providerName: result.name }), + }); + + createAction?.onAfter?.(data, result); + + queryClient.invalidateQueries({ queryKey: ssoProviderQueryKeys.list() }); + }, + onError: (error, data) => { + if ( + hasApiErrorBody(error) && + error.body?.status === 409 && + error.body?.type === 'https://auth0.com/api-errors#A0E-409-0001' + ) { + showToast({ + type: 'error', + message: t('notifications.provider_create_duplicated_provider_error', { + providerName: data.name, + }), + }); + return; + } + if (hasApiErrorBody(error)) { + const domainFromError = extractDomainFromDiscoveryError(error.body?.detail); + if (domainFromError) { + showToast({ + type: 'error', + message: t('notifications.provider_create_discovery_failure', { + domain: domainFromError, + }), + }); + return; + } + } + + handleError(error, { fallbackMessage: t('notifications.general_error') }); + }, + }); + + const createProvider = useCallback( + async (data: CreateIdentityProviderRequestContentPrivate): Promise => { + if (!coreClient) { + showToast({ + type: 'error', + message: t('notifications.general_error'), + }); + return; + } + + if (createAction?.onBefore) { + const canProceed = createAction.onBefore(data); + if (!canProceed) { + return; + } + } + + await createProviderMutation.mutateAsync(data); + }, + [coreClient, createAction, createProviderMutation], + ); + + return { + createProvider, + isCreating: createProviderMutation.isPending, + }; +} diff --git a/packages/react/src/hooks/my-organization/use-sso-provider-create-logic.ts b/packages/react/src/hooks/my-organization/use-sso-provider-create-logic.ts deleted file mode 100644 index c1f389b07..000000000 --- a/packages/react/src/hooks/my-organization/use-sso-provider-create-logic.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * SSO provider create logic hook. - * @module use-sso-provider-create-logic - * @internal - */ - -import { useCallback, useRef, useState } from 'react'; - -import { useConfig } from '@/hooks/my-organization/use-config'; -import { useIdpConfig } from '@/hooks/my-organization/use-idp-config'; -import type { - FormState, - ProviderConfigureHandle, - ProviderDetailsFormHandle, - UseSsoProviderCreateLogicOptions, - UseSsoProviderCreateLogicResult, -} from '@/types/my-organization/idp-management/sso-provider/sso-provider-create-types'; - -/** - * Hook for SSO provider create logic (form state, step actions, create handler). - * @param params - SsoProviderCreateLogicParams - * @returns formData, setFormData, createStepActions, handleCreate, detailsRef, configureRef - */ -export function useSsoProviderCreateLogic({ - onNext, - onPrevious, - createProvider, -}: UseSsoProviderCreateLogicOptions): UseSsoProviderCreateLogicResult { - const [formData, setFormData] = useState({}); - const { strategy, details, configure } = formData; - const detailsRef = useRef(null); - const configureRef = useRef(null); - const { isLoadingConfig, filteredStrategies } = useConfig(); - const { isLoadingIdpConfig, idpConfig } = useIdpConfig(); - - const createStepActions = useCallback( - ( - stepId: 'provider_details' | 'provider_configure', - ref: React.RefObject, - ) => { - const dataKey = stepId === 'provider_details' ? 'details' : 'configure'; - const handleAction = async ( - handler: typeof onNext | typeof onPrevious | undefined, - shouldValidate = false, - ): Promise => { - if (shouldValidate) { - const isValid = await ref.current?.validate(); - if (!isValid) return false; - } - const currentData = ref.current?.getData() ?? null; - setFormData((prev) => ({ ...prev, [dataKey]: currentData })); - if (!handler) return true; - const fullPayload = { ...formData, [dataKey]: currentData }; - return handler(stepId, fullPayload); - }; - return { - onNextAction: () => handleAction(onNext, true), - onPreviousAction: () => handleAction(onPrevious, false), - }; - }, - [formData, onNext, onPrevious], - ); - - const handleCreate = useCallback(async () => { - const finalConfigureData = configureRef.current?.getData(); - await createProvider({ - strategy: strategy!, - ...details!, - ...finalConfigureData, - }); - }, [strategy, details, configure, createProvider]); - - return { - formData, - setFormData, - createStepActions, - handleCreate, - detailsRef, - configureRef, - isLoadingConfig, - filteredStrategies, - isLoadingIdpConfig, - idpConfig, - }; -} diff --git a/packages/react/src/hooks/my-organization/use-sso-provider-create.ts b/packages/react/src/hooks/my-organization/use-sso-provider-create.ts index 559b8a84a..7cc9a1840 100644 --- a/packages/react/src/hooks/my-organization/use-sso-provider-create.ts +++ b/packages/react/src/hooks/my-organization/use-sso-provider-create.ts @@ -1,150 +1,101 @@ /** * SSO provider creation hook. + * Single public hook that consumes the internal service hook. * @module use-sso-provider-create */ -import { - hasApiErrorBody, - SsoProviderMappers, - type CreateIdentityProviderRequestContent, - type CreateIdentityProviderRequestContentPrivate, - type IdpKnownResponse, -} from '@auth0/universal-components-core'; -import { ssoProviderQueryKeys } from '@auth0/universal-components-core'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useCallback } from 'react'; +import { useCallback, useRef, useState } from 'react'; -import { showToast } from '@/components/auth0/shared/toast'; -import { useCoreClient } from '@/hooks/shared/use-core-client'; -import { useErrorHandler } from '@/hooks/shared/use-error-handler'; -import { useTranslator } from '@/hooks/shared/use-translator'; -import type { UseSsoProviderCreateOptions } from '@/types/my-organization/idp-management/sso-provider/sso-provider-create-types'; +import { useSsoProviderCreateService } from '@/hooks/my-organization/shared/services/use-sso-provider-create-service'; +import { useConfig } from '@/hooks/my-organization/use-config'; +import { useIdpConfig } from '@/hooks/my-organization/use-idp-config'; +import type { + FormState, + ProviderConfigureHandle, + ProviderDetailsFormHandle, + UseSsoProviderCreateOptions, + UseSsoProviderCreateResult, +} from '@/types/my-organization/idp-management/sso-provider/sso-provider-create-types'; -/** - * Extracts domain from discovery error detail. - * @param detail - Error detail string. - * @returns Domain string or null. - * @internal - */ -function extractDomainFromDiscoveryError(detail?: string): string | null { - if (!detail) return null; - const match = detail.match(/discovery failure:\s*(.+)/i); - return match?.[1]?.trim() ?? null; -} +export type { UseSsoProviderCreateResult }; -export interface UseSsoProviderCreateReturn { - createProvider: (data: CreateIdentityProviderRequestContentPrivate) => Promise; - isCreating: boolean; +export interface UseSsoProviderCreateHookOptions extends UseSsoProviderCreateOptions { + onNext?: (stepId: string, values: Partial) => boolean; + onPrevious?: (stepId: string, values: Partial) => boolean; } /** - * Hook for creating SSO identity providers. + * Hook for SSO provider creation. Manages form state, step navigation, + * and API operations through an internal service hook. * @param options - Hook options. - * @param options.createAction - Callback after successful creation. - * @param options.customMessages - Custom translation messages. - * @returns Hook state and methods + * @returns Form data, step actions, handlers, and loading states. */ export function useSsoProviderCreate({ createAction, customMessages = {}, -}: UseSsoProviderCreateOptions = {}): UseSsoProviderCreateReturn { - const { coreClient } = useCoreClient(); - const { t } = useTranslator('idp_management.create_sso_provider', customMessages); - const queryClient = useQueryClient(); - const handleError = useErrorHandler(); - const createProviderMutation = useMutation({ - mutationFn: async ( - data: CreateIdentityProviderRequestContentPrivate, - ): Promise => { - if (!coreClient) { - throw new Error('Core client not available'); - } - - const { strategy, name, display_name, ...configOptions } = data; - - const formData = { - strategy, - name, - display_name, - options: configOptions, - }; - - const apiRequestData: CreateIdentityProviderRequestContent = - SsoProviderMappers.createToAPI(formData); - - const result: IdpKnownResponse = await coreClient - .getMyOrganizationApiClient() - .organization.identityProviders.create(apiRequestData); - - return result; - }, - onSuccess: (result, data) => { - showToast({ - type: 'success', - message: t('notifications.provider_create_success', { providerName: result.name }), - }); - - createAction?.onAfter?.(data, result); - - // Invalidate the providers list to refetch with the new provider - queryClient.invalidateQueries({ queryKey: ssoProviderQueryKeys.list() }); - }, - onError: (error, data) => { - if ( - hasApiErrorBody(error) && - error.body?.status === 409 && - error.body?.type === 'https://auth0.com/api-errors#A0E-409-0001' - ) { - showToast({ - type: 'error', - message: t('notifications.provider_create_duplicated_provider_error', { - providerName: data.name, - }), - }); - return; - } - // Handle discovery failure error for domain - if (hasApiErrorBody(error)) { - const domainFromError = extractDomainFromDiscoveryError(error.body?.detail); - if (domainFromError) { - showToast({ - type: 'error', - message: t('notifications.provider_create_discovery_failure', { - domain: domainFromError, - }), - }); - return; - } - } - - handleError(error, { fallbackMessage: t('notifications.general_error') }); - }, + onNext, + onPrevious, +}: UseSsoProviderCreateHookOptions = {}): UseSsoProviderCreateResult { + const { createProvider, isCreating } = useSsoProviderCreateService({ + createAction, + customMessages, }); - const createProvider = useCallback( - async (data: CreateIdentityProviderRequestContentPrivate): Promise => { - if (!coreClient) { - showToast({ - type: 'error', - message: t('notifications.general_error'), - }); - return; - } - - if (createAction?.onBefore) { - const canProceed = createAction.onBefore(data); - if (!canProceed) { - return; + const [formData, setFormData] = useState({}); + const { strategy, details, configure } = formData; + const detailsRef = useRef(null); + const configureRef = useRef(null); + const { isLoadingConfig, filteredStrategies } = useConfig(); + const { isLoadingIdpConfig, idpConfig } = useIdpConfig(); + + const createStepActions = useCallback( + ( + stepId: 'provider_details' | 'provider_configure', + ref: React.RefObject, + ) => { + const dataKey = stepId === 'provider_details' ? 'details' : 'configure'; + const handleAction = async ( + handler: typeof onNext | typeof onPrevious | undefined, + shouldValidate = false, + ): Promise => { + if (shouldValidate) { + const isValid = await ref.current?.validate(); + if (!isValid) return false; } - } - - await createProviderMutation.mutateAsync(data); + const currentData = ref.current?.getData() ?? null; + setFormData((prev) => ({ ...prev, [dataKey]: currentData })); + if (!handler) return true; + const fullPayload = { ...formData, [dataKey]: currentData }; + return handler(stepId, fullPayload); + }; + return { + onNextAction: () => handleAction(onNext, true), + onPreviousAction: () => handleAction(onPrevious, false), + }; }, - [coreClient, createAction, createProviderMutation], + [formData, onNext, onPrevious], ); + const handleCreate = useCallback(async () => { + const finalConfigureData = configureRef.current?.getData(); + await createProvider({ + strategy: strategy!, + ...details!, + ...finalConfigureData, + }); + }, [strategy, details, configure, createProvider]); + return { - createProvider, - isCreating: createProviderMutation.isPending, + formData, + setFormData, + createStepActions, + handleCreate, + detailsRef, + configureRef, + isCreating, + isLoadingConfig, + filteredStrategies, + isLoadingIdpConfig, + idpConfig, }; } diff --git a/packages/react/src/types/my-organization/idp-management/sso-provider/sso-provider-create-types.ts b/packages/react/src/types/my-organization/idp-management/sso-provider/sso-provider-create-types.ts index b67ee2f5f..6f3b9e7cc 100644 --- a/packages/react/src/types/my-organization/idp-management/sso-provider/sso-provider-create-types.ts +++ b/packages/react/src/types/my-organization/idp-management/sso-provider/sso-provider-create-types.ts @@ -112,18 +112,13 @@ export interface UseSsoProviderCreateOptions { customMessages?: SsoProviderCreateProps['customMessages']; } -export interface UseSsoProviderCreateLogicOptions { - onNext?: SsoProviderCreateProps['onNext']; - onPrevious?: SsoProviderCreateProps['onPrevious']; - createProvider: (data: CreateIdentityProviderRequestContentPrivate) => Promise; -} - -export interface UseSsoProviderCreateLogicResult { +export interface UseSsoProviderCreateResult { formData: FormState; setFormData: React.Dispatch>; detailsRef: React.RefObject; configureRef: React.RefObject; handleCreate: () => Promise; + isCreating: boolean; isLoadingConfig: boolean; filteredStrategies: IdpStrategy[]; isLoadingIdpConfig: boolean;