diff --git a/src/hooks/__tests__/useLoadConnect-test.tsx b/src/hooks/__tests__/useLoadConnect-test.tsx
index 5ddeab80a7..676cc00b25 100644
--- a/src/hooks/__tests__/useLoadConnect-test.tsx
+++ b/src/hooks/__tests__/useLoadConnect-test.tsx
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'
-import { useSelector } from 'react-redux'
+import { useDispatch, useSelector } from 'react-redux'
import type { RootState } from 'src/redux/Store'
import { screen, render } from 'src/utilities/testingLibrary'
import useLoadConnect from 'src/hooks/useLoadConnect'
@@ -9,10 +9,13 @@ import { ApiProvider } from 'src/context/ApiContext'
import { apiValue } from 'src/const/apiProviderMock'
import { ConfigError } from 'src/components/ConfigError'
import { COMBO_JOB_DATA_TYPES } from 'src/const/comboJobDataTypes'
+import { loadExperimentalFeatures } from 'src/redux/reducers/experimentalFeaturesSlice'
-const TestLoadConnectComponent: React.FC<{ clientConfig: ClientConfigType }> = ({
- clientConfig,
-}) => {
+const TestLoadConnectComponent: React.FC<{
+ clientConfig: ClientConfigType
+ experimentalFeatures?: { unavailableInstitutions: { guid: string; name: string }[] }
+}> = ({ clientConfig, experimentalFeatures }) => {
+ const dispatch = useDispatch()
const step = useSelector(
(state: RootState) =>
state.connect.location[state.connect.location.length - 1]?.step ?? STEPS.SEARCH,
@@ -21,6 +24,7 @@ const TestLoadConnectComponent: React.FC<{ clientConfig: ClientConfigType }> = (
const { loadConnect } = useLoadConnect()
useEffect(() => {
+ dispatch(loadExperimentalFeatures(experimentalFeatures || {}))
loadConnect(clientConfig)
}, [])
@@ -35,6 +39,8 @@ const TestLoadConnectComponent: React.FC<{ clientConfig: ClientConfigType }> = (
return
Search
} else if (step === STEPS.ENTER_CREDENTIALS) {
return Enter credentials
+ } else if (step === STEPS.INSTITUTION_STATUS_DETAILS) {
+ return Institution status details
} else {
return Search
}
@@ -302,4 +308,33 @@ describe('useLoadConnect', () => {
),
).toBeInTheDocument()
})
+
+ it('will return the INSTITUTION_STATUS_DETAILS step if the state contains a configured unavailable institution', async () => {
+ const mockApi = {
+ ...apiValue,
+ loadInstitutionByGuid: vi.fn().mockResolvedValue(
+ Promise.resolve({
+ ...institutionData.institution,
+ guid: 'INS-unavailable',
+ name: 'Unavailable Bank',
+ }),
+ ),
+ }
+ render(
+
+
+ ,
+ )
+ expect(await screen.findByText(/Institution status details/i)).toBeInTheDocument()
+ })
})
diff --git a/src/hooks/useLoadConnect.tsx b/src/hooks/useLoadConnect.tsx
index d2e17724b7..8bbd416c60 100644
--- a/src/hooks/useLoadConnect.tsx
+++ b/src/hooks/useLoadConnect.tsx
@@ -17,6 +17,7 @@ import { useApi, ApiContextTypes } from 'src/context/ApiContext'
import { __ } from 'src/utilities/Intl'
import type { RootState } from 'src/redux/Store'
import { instutionSupportRequestedProducts } from 'src/utilities/Institution'
+import { getExperimentalFeatures } from 'src/redux/reducers/experimentalFeaturesSlice'
export const getErrorResource = (err: { config: { url: string | string[] } }) => {
if (err.config?.url.includes('/institutions')) {
@@ -47,6 +48,7 @@ export const getErrorResource = (err: { config: { url: string | string[] } }) =>
const useLoadConnect = () => {
const { api } = useApi()
const profiles = useSelector((state: RootState) => state.profiles)
+ const experimentalFeatures = useSelector(getExperimentalFeatures)
const clientLocale = useMemo(() => {
return document.querySelector('html')?.getAttribute('lang') || 'en'
}, [document.querySelector('html')?.getAttribute('lang')])
@@ -77,6 +79,7 @@ const useLoadConnect = () => {
return from(api.loadMembers(clientLocale)).pipe(
map((members = []) =>
loadConnectSuccess({
+ experimentalFeatures,
members,
widgetProfile: profiles.widgetProfile,
...dependencies,
diff --git a/src/redux/reducers/Connect.js b/src/redux/reducers/Connect.js
index 2a68614b04..0bc93e513a 100644
--- a/src/redux/reducers/Connect.js
+++ b/src/redux/reducers/Connect.js
@@ -59,6 +59,7 @@ const loadConnectSuccess = (state, action) => {
microdeposit,
config = {},
institution = {},
+ experimentalFeatures = {},
widgetProfile,
} = action.payload
@@ -70,7 +71,15 @@ const loadConnectSuccess = (state, action) => {
isComponentLoading: false,
location: pushLocation(
state.location,
- getStartingStep(members, member, microdeposit, config, institution, widgetProfile),
+ getStartingStep(
+ members,
+ member,
+ microdeposit,
+ config,
+ institution,
+ widgetProfile,
+ experimentalFeatures,
+ ),
),
selectedInstitution: institution,
updateCredentials:
@@ -520,7 +529,24 @@ const upsertMember = (state, action) => {
return [...state.members, loadedMember]
}
-function getStartingStep(members, member, microdeposit, config, institution, widgetProfile) {
+function getStartingStep(
+ members,
+ member,
+ microdeposit,
+ config,
+ institution,
+ widgetProfile,
+ experimentalFeatures = {},
+) {
+ // Unavailable institutions experimental feature: Make sure we don't load a user
+ // directly to an institution that should be unavailable.
+ const unavailableInstitutions = experimentalFeatures?.unavailableInstitutions || []
+ const institutionIsAvailable =
+ institution &&
+ unavailableInstitutions.find(
+ (ins) => ins.guid === institution?.guid || ins.name === institution?.name,
+ ) === undefined
+
const shouldStepToMFA =
member && config.update_credentials && member.connection_status === ReadableStatuses.CHALLENGED
const shouldUpdateCredentials =
@@ -530,13 +556,16 @@ function getStartingStep(members, member, microdeposit, config, institution, wid
config.mode === VERIFY_MODE &&
microdeposit.status !== MicrodepositsStatuses.PREINITIATED
const shouldLoadWithInstitution =
- institution && (config.current_institution_guid || config.current_institution_code)
+ institution &&
+ (config.current_institution_guid || config.current_institution_code) &&
+ institutionIsAvailable
const shouldStepToConnecting =
member?.connection_status === ReadableStatuses.REJECTED ||
member?.connection_status === ReadableStatuses.EXPIRED
const shouldStepToInstitutionStatusDetails =
(institution && institutionIsBlockedForCostReasons(institution)) ||
- (member && memberIsBlockedForCostReasons(member))
+ (member && memberIsBlockedForCostReasons(member)) ||
+ !institutionIsAvailable
if (shouldStepToInstitutionStatusDetails) {
return STEPS.INSTITUTION_STATUS_DETAILS
diff --git a/src/redux/reducers/__tests__/Connect-test.js b/src/redux/reducers/__tests__/Connect-test.js
index d89843d143..578f50ec13 100644
--- a/src/redux/reducers/__tests__/Connect-test.js
+++ b/src/redux/reducers/__tests__/Connect-test.js
@@ -312,6 +312,82 @@ describe('Connect redux store', () => {
expect(afterState.members).toHaveLength(2)
expect(afterState.members[0]).toEqual({ guid: 'MBR-1', institution_guid: 'INST-1' })
})
+
+ it('should show the institutionStatusDetails step if the configured institution is blocked for fees/costs', () => {
+ const afterState = reducer(
+ defaultState,
+ loadConnectSuccess({
+ config: { current_institution_guid: 'INS-1' },
+ institution: { guid: 'INS-1', name: 'Chase Bank', is_disabled_by_client: true },
+ widgetProfile: {},
+ }),
+ )
+
+ expect(afterState.location[afterState.location.length - 1].step).toEqual(
+ STEPS.INSTITUTION_STATUS_DETAILS,
+ )
+ })
+
+ it('should show the credentials step if the configured institution is not blocked by the client for fees/costs', () => {
+ const afterState = reducer(
+ defaultState,
+ loadConnectSuccess({
+ config: { current_institution_guid: 'INS-1' },
+ institution: { guid: 'INS-1', name: 'Chase Bank', is_disabled_by_client: false },
+ widgetProfile: {},
+ }),
+ )
+
+ expect(afterState.location[afterState.location.length - 1].step).toEqual(
+ STEPS.ENTER_CREDENTIALS,
+ )
+ })
+
+ it('should show the institutionStatusDetails step if the configured institution is unavailable', () => {
+ const afterState = reducer(
+ defaultState,
+ loadConnectSuccess({
+ institution: { guid: 'INS-1', name: 'Unavailable Bank' },
+ experimentalFeatures: {
+ unavailableInstitutions: [{ guid: 'INS-1', name: 'Unavailable Bank' }],
+ },
+ }),
+ )
+
+ expect(afterState.location[afterState.location.length - 1].step).toEqual(
+ STEPS.INSTITUTION_STATUS_DETAILS,
+ )
+ })
+
+ it('should show the credentials step if the configured institution_guid is available', () => {
+ const afterState = reducer(
+ defaultState,
+ loadConnectSuccess({
+ config: { current_institution_guid: 'INS-1' },
+ institution: { guid: 'INS-1', name: 'Unavailable Bank' },
+ widgetProfile: {},
+ }),
+ )
+
+ expect(afterState.location[afterState.location.length - 1].step).toEqual(
+ STEPS.ENTER_CREDENTIALS,
+ )
+ })
+
+ it('should show the credentials step if the configured institution code is available', () => {
+ const afterState = reducer(
+ defaultState,
+ loadConnectSuccess({
+ config: { current_institution_code: 'unavailable_bank' },
+ institution: { guid: 'INS-1', name: 'Unavailable Bank' },
+ widgetProfile: {},
+ }),
+ )
+
+ expect(afterState.location[afterState.location.length - 1].step).toEqual(
+ STEPS.ENTER_CREDENTIALS,
+ )
+ })
})
describe('loadConnectError', () => {