diff --git a/src/const/language/es.json b/src/const/language/es.json
index cfb6897627..a3ff3087f4 100644
--- a/src/const/language/es.json
+++ b/src/const/language/es.json
@@ -406,8 +406,6 @@
"Unable to connect": "No se puede conectar",
"Maintenance in progress": "Mantenimiento en curso",
"Deposits verified. You're almost done setting things up. Continue to your institution.": "Depósitos verificados. Ya casi terminas de configurar todo. Continúa con tu institución.",
- "After logging in, share at least one account and %1profile information%2.": "Después de iniciar sesión, comparta al menos una cuenta y %1información de perfil%2.",
- "After logging in, share at least one account.": "Después de iniciar sesión, comparta al menos una cuenta.",
"Connection not supported by %1": "Conexión no compatible con %1",
"%1 currently limits how your data can be shared. We'll enable this connection once %1 opens access.": "%1 actualmente limita cómo se pueden compartir sus datos. Habilitaremos esta conexión una vez que %1 abra el acceso.",
"UNAVAILABLE": "INDISPONIBLE",
@@ -421,6 +419,8 @@
"Information to select on the %1 site": "Información para seleccionar en el sitio %1",
"Checking or savings account": "Cuenta corriente o de ahorros",
"Profile information": "Información del perfil",
+ "Account numbers": "Números de cuenta",
+ "To complete your connection, please %1share%2 the following after signing in:": "Para completar su conexión, por favor, %1comparta%2 lo siguiente después de iniciar sesión:",
"connect/disclosure/button\u0004Continue": "Continuar",
"connect/disclosure/policy/text\u0004By clicking Continue, you agree to the ": "Al hacer clic en Continuar, tu aceptas la ",
"connect/disclosure/policy/link\u0004MX Privacy Policy.": "Política de privacidad de Money Experience.",
diff --git a/src/const/language/es.po b/src/const/language/es.po
index f1826546ef..11247d9da6 100644
--- a/src/const/language/es.po
+++ b/src/const/language/es.po
@@ -891,6 +891,7 @@ msgid "Basic account information"
msgstr "Información básica de la cuenta"
#: src/views/disclosure/Disclosure.js
+#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Tax documents"
msgstr "Documentos fiscales"
@@ -1422,6 +1423,7 @@ msgstr ""
"y descripciones."
#: src/const/DataClusters.js
+#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Statements"
msgstr "Declaraciones"
@@ -2036,17 +2038,6 @@ msgstr ""
"Depósitos verificados. Ya casi terminas de configurar todo. Continúa con tu "
"institución."
-#: src/views/oauth/experiments/PredirectInstructions.tsx
-msgid ""
-"After logging in, share at least one account and %1profile information%2."
-msgstr ""
-"Después de iniciar sesión, comparta al menos una cuenta y %1información de "
-"perfil%2."
-
-#: src/views/oauth/experiments/PredirectInstructions.tsx
-msgid "After logging in, share at least one account."
-msgstr "Después de iniciar sesión, comparta al menos una cuenta."
-
#: src/utilities/institutionStatus.ts
msgid "Connection not supported by %1"
msgstr "Conexión no compatible con %1"
@@ -2102,3 +2093,14 @@ msgstr "Cuenta corriente o de ahorros"
#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Profile information"
msgstr "Información del perfil"
+
+#: src/views/oauth/experiments/PredirectInstructions.tsx
+msgid "Account numbers"
+msgstr "Números de cuenta"
+
+#: src/views/oauth/experiments/PredirectInstructions.tsx
+msgid ""
+"To complete your connection, please %1share%2 the following after signing in:"
+msgstr ""
+"Para completar su conexión, por favor, %1comparta%2 lo siguiente después de "
+"iniciar sesión:"
diff --git a/src/const/language/frCa.json b/src/const/language/frCa.json
index ad51c0de79..81f997538e 100644
--- a/src/const/language/frCa.json
+++ b/src/const/language/frCa.json
@@ -407,8 +407,6 @@
"Unable to connect": "Impossible de se connecter",
"Maintenance in progress": "Entretien en cours",
"Deposits verified. You're almost done setting things up. Continue to your institution.": "Dépôts vérifiés. Vous avez presque terminé la configuration. Rendez-vous dans votre établissement.",
- "After logging in, share at least one account and %1profile information%2.": "Après vous être connecté, partagez au moins un compte et %1informations de profil%2.",
- "After logging in, share at least one account.": "Après vous être connecté, partagez au moins un compte.",
"Connection not supported by %1": "Connexion non prise en charge par %1",
"%1 currently limits how your data can be shared. We'll enable this connection once %1 opens access.": "%1 limite actuellement la manière dont vos données peuvent être partagées. Nous activerons cette connexion une fois que %1 ouvrira l'accès.",
"UNAVAILABLE": "INDISPONIBLE",
@@ -422,6 +420,8 @@
"Information to select on the %1 site": "Informations à sélectionner sur le site %1.",
"Checking or savings account": "Compte courant ou compte d'épargne",
"Profile information": "Informations de profil",
+ "Account numbers": "Numéros de compte",
+ "To complete your connection, please %1share%2 the following after signing in:": "Pour finaliser votre connexion, veuillez %1partager%2 les informations suivantes après vous être connecté :",
"connect/disclosure/policy/text\u0004By clicking Continue, you agree to the ": "En cliquant sur Continuer, vous acceptez la ",
"connect/disclosure/policy/link\u0004MX Privacy Policy.": "Politique de confidentialité de MX.",
"connect/disclosure/policy/link\u0004MX Privacy Policy": "Politique de confidentialité de MX.",
diff --git a/src/const/language/frCa.po b/src/const/language/frCa.po
index 977befd191..02a7af67e1 100644
--- a/src/const/language/frCa.po
+++ b/src/const/language/frCa.po
@@ -983,6 +983,7 @@ msgid "Basic account information"
msgstr "Informations de base sur le compte"
#: src/views/disclosure/Disclosure.js
+#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Tax documents"
msgstr "Documents fiscaux"
@@ -1514,6 +1515,7 @@ msgstr ""
"dates et descriptions."
#: src/const/DataClusters.js
+#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Statements"
msgstr "Déclarations"
@@ -2114,17 +2116,6 @@ msgstr ""
"Dépôts vérifiés. Vous avez presque terminé la configuration. Rendez-vous "
"dans votre établissement."
-#: src/views/oauth/experiments/PredirectInstructions.tsx
-msgid ""
-"After logging in, share at least one account and %1profile information%2."
-msgstr ""
-"Après vous être connecté, partagez au moins un compte et %1informations de "
-"profil%2."
-
-#: src/views/oauth/experiments/PredirectInstructions.tsx
-msgid "After logging in, share at least one account."
-msgstr "Après vous être connecté, partagez au moins un compte."
-
#: src/utilities/institutionStatus.ts
msgid "Connection not supported by %1"
msgstr "Connexion non prise en charge par %1"
@@ -2180,3 +2171,14 @@ msgstr "Compte courant ou compte d'épargne"
#: src/views/oauth/experiments/PredirectInstructions.tsx
msgid "Profile information"
msgstr "Informations de profil"
+
+#: src/views/oauth/experiments/PredirectInstructions.tsx
+msgid "Account numbers"
+msgstr "Numéros de compte"
+
+#: src/views/oauth/experiments/PredirectInstructions.tsx
+msgid ""
+"To complete your connection, please %1share%2 the following after signing in:"
+msgstr ""
+"Pour finaliser votre connexion, veuillez %1partager%2 les informations "
+"suivantes après vous être connecté :"
diff --git a/src/services/mockedData.ts b/src/services/mockedData.ts
index 726c0c86da..a5f2bc3a9b 100644
--- a/src/services/mockedData.ts
+++ b/src/services/mockedData.ts
@@ -420,12 +420,13 @@ export const USER_DATA = {
created_at: 1661194428,
}
-export const FAVORITE_INSTITUTIONS = [
+export const FAVORITE_INSTITUTIONS: InstitutionResponseType[] = [
{
account_verification_is_enabled: true,
account_identification_is_enabled: true,
code: 'gringotts',
guid: 'INS-123',
+ is_disabled_by_client: false,
login_url: null,
name: 'Gringotts',
popularity: 43985,
@@ -438,6 +439,7 @@ export const FAVORITE_INSTITUTIONS = [
account_identification_is_enabled: true,
code: '77277',
guid: 'INS-345',
+ is_disabled_by_client: false,
login_url: 'https://www.americanexpress.com/en-us/account/login/',
name: 'American Express Credit Card',
popularity: 20,
@@ -450,6 +452,7 @@ export const FAVORITE_INSTITUTIONS = [
account_identification_is_enabled: false,
code: '78033',
guid: 'INS-567',
+ is_disabled_by_client: false,
login_url: null,
name: 'Discover Credit Card',
popularity: 9,
@@ -462,6 +465,7 @@ export const FAVORITE_INSTITUTIONS = [
account_identification_is_enabled: true,
code: '1d303f53-a9c2-4819-9469-9320b561280b',
guid: 'INS-789',
+ is_disabled_by_client: false,
login_url: null,
name: 'Capital One',
popularity: 9,
@@ -484,12 +488,13 @@ export const FAVORITE_INSTITUTIONS = [
},
]
-export const SEARCHED_INSTITUTIONS = [
+export const SEARCHED_INSTITUTIONS: InstitutionResponseType[] = [
{
account_verification_is_enabled: true,
account_identification_is_enabled: true,
code: 'gringotts',
guid: 'INS-f1a3285d-e855-b68f-6aa7-8ae775c0e0e9',
+ is_disabled_by_client: false,
login_url: null,
name: 'Gringotts',
popularity: 43984,
@@ -502,6 +507,7 @@ export const SEARCHED_INSTITUTIONS = [
account_identification_is_enabled: false,
code: '043ff29f-ff1b-43ac-936f-27d26403c6aa',
guid: 'INS-39fc8bea-4568-40ce-95d5-c2ea33a86398',
+ is_disabled_by_client: false,
login_url: null,
name: 'MX Bank',
popularity: 3,
@@ -514,6 +520,7 @@ export const SEARCHED_INSTITUTIONS = [
account_identification_is_enabled: false,
code: '11166c24-99c4-4552-a6a2-4a4706abf9b0',
guid: 'INS-c706ddb2-dfee-4575-a1ce-df2f907ab4af',
+ is_disabled_by_client: false,
login_url: 'https://mx.com',
name: 'Gringotts Oauth/MDX V50',
popularity: 1,
@@ -526,6 +533,7 @@ export const SEARCHED_INSTITUTIONS = [
account_identification_is_enabled: false,
code: '4a32a8d9-44e8-4302-a1a5-e37c109eead4',
guid: 'INS-f8968535-d8e1-45e9-8d0e-80bdcaaeb0fd',
+ is_disabled_by_client: false,
login_url: null,
name: 'Gringotts TEST(Clone)',
popularity: 0,
@@ -538,6 +546,7 @@ export const SEARCHED_INSTITUTIONS = [
account_identification_is_enabled: false,
code: '83ee1118-4ae9-4140-a501-8b74c2f60cbe',
guid: 'INS-83914605-0efa-45e5-b1f2-b5a9a0afa909',
+ is_disabled_by_client: false,
login_url: null,
name: 'Grinnell State Bank',
popularity: 0,
diff --git a/src/views/oauth/OAuthDefault.js b/src/views/oauth/OAuthDefault.js
index 8719d3f44a..646a7d1ef8 100644
--- a/src/views/oauth/OAuthDefault.js
+++ b/src/views/oauth/OAuthDefault.js
@@ -20,25 +20,17 @@ import useAnalyticsPath from 'src/hooks/useAnalyticsPath'
import useAnalyticsEvent from 'src/hooks/useAnalyticsEvent'
import { AnalyticEvents, PageviewInfo } from 'src/const/Analytics'
import { useApi } from 'src/context/ApiContext'
-import { getUserFeatures } from 'src/redux/reducers/userFeaturesSlice'
-import {
- PredirectInstructions,
- WELLS_FARGO_INSTRUCTIONS_FEATURE_NAME,
-} from 'src/views/oauth/experiments/PredirectInstructions'
+import { PredirectInstructions } from 'src/views/oauth/experiments/PredirectInstructions'
+import { isWellsFargoInstitution } from 'src/views/oauth/experiments/predirectInstructionsUtils'
export const OAuthDefault = (props) => {
// Experiment code - Remove after experiment is over
const language = window?.app?.options?.language || 'en-US'
- const userFeatures = useSelector(getUserFeatures)
- const isWellsFargoInstructionsFeatureEnabled =
- userFeatures.some(
- (feature) =>
- feature.feature_name === WELLS_FARGO_INSTRUCTIONS_FEATURE_NAME &&
- feature.is_enabled === 'test',
- ) &&
- (props.institution.guid === 'INS-6073ad01-da9e-f6ba-dfdf-5f1500d8e867' || // Wells Fargo PROD guid
- props.institution.guid === 'INS-f9e8d5f6-b953-da63-32e4-6e88fbe8b250') && // Wells Fargo SAND guid for testing
- language.toLowerCase() === 'en-us'
+ const isWellsFargo = isWellsFargoInstitution(props.institution)
+
+ const hasPredirectInstructions =
+ Array.isArray(props.institution?.oauth_predirect_instructions) &&
+ props.institution?.oauth_predirect_instructions.length > 0
const { api } = useApi()
useAnalyticsPath(...PageviewInfo.CONNECT_OAUTH_INSTRUCTIONS, {
@@ -58,11 +50,12 @@ export const OAuthDefault = (props) => {
return (
- {isWellsFargoInstructionsFeatureEnabled ? (
+ {/* This check allows us to merge our frontend code before the backend is ready.
+ Wells Fargo will continue to get the special treatment, and other institutions
+ will only start seeing the pre-redirect instructions once the backend is ready. */}
+ {isWellsFargo || hasPredirectInstructions ? (
<>
- {/* // This experiment removes the institution block and completely changes the instructional
- text */}
-
+
>
) : (
<>
diff --git a/src/views/oauth/experiments/PredirectInstructions.tsx b/src/views/oauth/experiments/PredirectInstructions.tsx
index 73ca617b16..d5f82e0c9a 100644
--- a/src/views/oauth/experiments/PredirectInstructions.tsx
+++ b/src/views/oauth/experiments/PredirectInstructions.tsx
@@ -1,50 +1,84 @@
import React from 'react'
-import { useSelector } from 'react-redux'
import 'src/views/oauth/experiments/PredirectInstructions.css'
-import { selectConnectConfig } from 'src/redux/reducers/configSlice'
-
-import { Icon, IconWeight, Text } from '@mxenabled/mxui'
+import { Text } from '@mxenabled/mxui'
import { __ } from 'src/utilities/Intl'
import { Divider, Paper } from '@mui/material'
import { ExampleCheckbox } from 'src/components/ExampleCheckbox'
+import {
+ getInstitutionBrandColor,
+ isWellsFargoInstitution,
+ OAUTH_PREDIRECT_INSTRUCTION,
+} from 'src/views/oauth/experiments/predirectInstructionsUtils'
export const WELLS_FARGO_INSTRUCTIONS_FEATURE_NAME = 'WELLS_FARGO_INSTRUCTIONS'
+export const DEFAULT_HEADER_HEX_COLOR = '#444444'
-function PredirectInstructions(props: React.FunctionComponent & { institutionName: string }) {
- const config = useSelector(selectConnectConfig)
- const products = config?.data_request?.products || []
- const showProfileSelection =
- products.includes('account_verification') || products.includes('identity_verification')
+function PredirectInstructions(
+ props: React.FunctionComponent & {
+ institution: InstitutionResponseType
+ },
+) {
+ // Filter out any invalid instruction values
+ const configuredPredirectInstructions = Array.isArray(
+ props.institution?.oauth_predirect_instructions,
+ )
+ ? [...props.institution.oauth_predirect_instructions].filter((instruction) =>
+ Object.values(OAUTH_PREDIRECT_INSTRUCTION).includes(instruction),
+ )
+ : []
- const institutionColor = '#d9181f' // Wells Fargo red
+ // Give Wells Fargo a default predirect instruction if none are configured, because we experimented on
+ // Wells Fargo, and want to maintain the experience, until it is fully configured in the backend.
+ if (isWellsFargoInstitution(props.institution) && configuredPredirectInstructions.length === 0) {
+ configuredPredirectInstructions.push(
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ )
- const uiElementTypes = {
- CHECKING_OR_SAVINGS_ACCOUNT: 'checking-or-savings-account',
- DIVIDER: 'divider',
- PROFILE_INFORMATION: 'profile',
+ configuredPredirectInstructions.push(
+ OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION,
+ )
}
- const checkboxItems = [uiElementTypes.CHECKING_OR_SAVINGS_ACCOUNT]
- if (showProfileSelection) {
- checkboxItems.push(uiElementTypes.DIVIDER)
- checkboxItems.push(uiElementTypes.PROFILE_INFORMATION)
+ // If the instructions are still empty, provide a default of account and transactions
+ // for a better user experience.
+ if (configuredPredirectInstructions.length === 0) {
+ configuredPredirectInstructions.push(
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ )
+ }
+
+ const institutionColor = getInstitutionBrandColor(props.institution, DEFAULT_HEADER_HEX_COLOR)
+
+ const uiElementTypes = {
+ [OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION]:
+ 'checking-or-savings-account',
+ [OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_NUMBERS_INSTRUCTION]: 'account-numbers',
+ [OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION]: 'profile',
+ [OAUTH_PREDIRECT_INSTRUCTION.STATEMENTS_INSTRUCTION]: 'statements',
+ [OAUTH_PREDIRECT_INSTRUCTION.TAX_INSTRUCTION]: 'tax',
}
+ const checkboxItems: string[] = []
+ configuredPredirectInstructions.forEach((instruction) => {
+ const uiElementType = uiElementTypes[instruction]
+ if (uiElementType) {
+ checkboxItems.push(uiElementType)
+ }
+ })
+
/* Bold text is needed. The styles applied to this text prevent server-provided styles from ruining strong elements */
- const instructionText = showProfileSelection
- ? __(
- 'After logging in, share at least one account and %1profile information%2.',
- "
",
- '',
- )
- : __('After logging in, share at least one account.')
+ const instructionText = __(
+ 'To complete your connection, please %1share%2 the following after signing in:',
+ "
",
+ '',
+ )
return (
<>
- {__('Log in at %1', props.institutionName)}
+ {__('Log in at %1', props.institution.name)}
- {showProfileSelection && (
-
- )}
@@ -66,25 +97,37 @@ function PredirectInstructions(props: React.FunctionComponent & { institutionNam
{/* Inline color and font styles on the header and text because this is a dynamic area */}
- {props.institutionName}
+ {props.institution.name}
-
+
{checkboxItems.map((item, index) => {
- if (item === uiElementTypes.DIVIDER) {
- return
- } else {
- let text = ''
- if (item === uiElementTypes.CHECKING_OR_SAVINGS_ACCOUNT) {
+ let text = ''
+ switch (item) {
+ case uiElementTypes[
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION
+ ]:
text = __('Checking or savings account')
- } else if (item === uiElementTypes.PROFILE_INFORMATION) {
+ break
+ case uiElementTypes[OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_NUMBERS_INSTRUCTION]:
+ text = __('Account numbers')
+ break
+ case uiElementTypes[OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION]:
text = __('Profile information')
- }
+ break
+ case uiElementTypes[OAUTH_PREDIRECT_INSTRUCTION.STATEMENTS_INSTRUCTION]:
+ text = __('Statements')
+ break
+ case uiElementTypes[OAUTH_PREDIRECT_INSTRUCTION.TAX_INSTRUCTION]:
+ text = __('Tax documents')
+ break
+ }
- const isLastItem = index === checkboxItems.length - 1
+ const isLastItem = index === checkboxItems.length - 1
- return (
+ return (
+ <>
-
- )
- }
+
+ {!isLastItem && }
+ >
+ )
})}
diff --git a/src/views/oauth/experiments/__tests__/PredirectInstructions-test.tsx b/src/views/oauth/experiments/__tests__/PredirectInstructions-test.tsx
index 9560273c13..fe16706133 100644
--- a/src/views/oauth/experiments/__tests__/PredirectInstructions-test.tsx
+++ b/src/views/oauth/experiments/__tests__/PredirectInstructions-test.tsx
@@ -3,45 +3,26 @@ import { expect, vi } from 'vitest'
import { createTestReduxStore, render, screen } from 'src/utilities/testingLibrary'
import { OAuthDefault } from 'src/views/oauth/OAuthDefault'
import { ApiContextTypes } from 'src/context/ApiContext'
+import { DEFAULT_HEADER_HEX_COLOR } from 'src/views/oauth/experiments/PredirectInstructions'
+import { OAUTH_PREDIRECT_INSTRUCTION } from 'src/views/oauth/experiments/predirectInstructionsUtils'
describe('
PredirectInstructions test', () => {
- it('can show the instructions for verification/identity', async () => {
+ it('wells fargo can show the instructions for verification/identity', async () => {
const onSignInClick = vi.fn()
const onAnalyticsEvent = vi.fn()
- // This set of instructions only shows for the Wells Fargo institution (at the moment)
const institution = {
guid: 'INS-f9e8d5f6-b953-da63-32e4-6e88fbe8b250',
name: 'Wells Fargo',
testProp: 'testValue',
+ oauth_predirect_instructions: [
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION,
+ ],
}
const member = { guid: 'testGuid' }
const store = createTestReduxStore({
- config: {
- _initialValues: '',
- is_mobile_webview: false,
- target_origin_referrer: null,
- ui_message_version: 4,
- ui_message_protocol: 'post_message',
- ui_message_webview_url_scheme: '',
- color_scheme: 'light',
- current_institution_code: null,
- current_institution_guid: null,
- current_member_guid: null,
- current_microdeposit_guid: null,
- enable_app2app: false,
- disable_background_agg: false,
- disable_institution_search: false,
- include_identity: false,
- include_transactions: false,
- iso_country_code: null,
- oauth_referral_source: '',
- update_credentials: false,
- wait_for_full_aggregation: false,
- mode: 'verification',
- data_request: { products: ['account_verification'] },
- },
connect: {
isOauthLoading: false,
oauthURL: 'testUrl',
@@ -49,15 +30,6 @@ describe('
PredirectInstructions test', () => {
name: 'Wells Fargo',
},
},
- userFeatures: {
- items: [
- // This item indicates that the feature flag is enabled for the Wells Fargo instructions
- {
- is_enabled: 'test',
- feature_name: 'WELLS_FARGO_INSTRUCTIONS',
- },
- ],
- },
})
const apiValue = {
@@ -82,28 +54,34 @@ describe('
PredirectInstructions test', () => {
// See PredirectInstructions.tsx for the text we are verifying here
expect(screen.getByText('Wells Fargo')).toBeInTheDocument()
expect(screen.getByText('Log in at Wells Fargo', { selector: 'h2' })).toBeInTheDocument()
+
expect(
screen.getByText((content, element) => {
if (!element) return false
return (
element.classList.contains('predirect-instruction-text') &&
- content.startsWith('After logging in, share at least one account and') &&
+ content.startsWith('To complete your connection, please') &&
element.textContent ===
- 'After logging in, share at least one account and profile information.'
+ 'To complete your connection, please share the following after signing in:'
)
}),
).toBeInTheDocument()
+
+ expect(screen.getByText('Checking or savings account')).toBeInTheDocument()
+ expect(screen.getByText('Profile information')).toBeInTheDocument()
})
- it('can show instructions for aggregation-only', async () => {
+ it('wells fargo can show instructions for aggregation-only', async () => {
const onSignInClick = vi.fn()
const onAnalyticsEvent = vi.fn()
- // This set of instructions only shows for the Wells Fargo institution (at the moment)
const institution = {
guid: 'INS-f9e8d5f6-b953-da63-32e4-6e88fbe8b250',
name: 'Wells Fargo',
testProp: 'testValue',
+ oauth_predirect_instructions: [
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ ],
}
const member = { guid: 'testGuid' }
@@ -115,15 +93,6 @@ describe('
PredirectInstructions test', () => {
name: 'Wells Fargo',
},
},
- userFeatures: {
- items: [
- // This item indicates that the feature flag is enabled for the Wells Fargo instructions
- {
- is_enabled: 'test',
- feature_name: 'WELLS_FARGO_INSTRUCTIONS',
- },
- ],
- },
})
const apiValue = {
@@ -148,6 +117,206 @@ describe('
PredirectInstructions test', () => {
// See PredirectInstructions.tsx for the text we are verifying here
expect(screen.getByText('Wells Fargo')).toBeInTheDocument()
expect(screen.getByText('Log in at Wells Fargo', { selector: 'h2' })).toBeInTheDocument()
- expect(screen.getByText('After logging in, share at least one account.')).toBeInTheDocument()
+
+ expect(
+ screen.getByText((content, element) => {
+ if (!element) return false
+ return (
+ element.classList.contains('predirect-instruction-text') &&
+ content.startsWith('To complete your connection, please') &&
+ element.textContent ===
+ 'To complete your connection, please share the following after signing in:'
+ )
+ }),
+ ).toBeInTheDocument()
+ expect(screen.getByText('Checking or savings account')).toBeInTheDocument()
+ })
+
+ it('an institution with predirect instructions can show the default header color', async () => {
+ const onSignInClick = vi.fn()
+ const onAnalyticsEvent = vi.fn()
+
+ const institution = {
+ guid: 'INS-test',
+ name: 'Test Bank',
+ oauth_predirect_instructions: [
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION,
+ ],
+ testProp: 'testValue',
+ }
+ const member = { guid: 'testGuid' }
+
+ const store = createTestReduxStore({
+ connect: {
+ isOauthLoading: false,
+ oauthURL: 'testUrl',
+ selectedInstitution: {
+ name: 'Test Bank',
+ },
+ },
+ })
+
+ const apiValue = {
+ oAuthStart: vi.fn(),
+ } as unknown as ApiContextTypes
+
+ render(
+
{}}
+ />,
+ {
+ apiValue,
+ onAnalyticsEvent,
+ store,
+ },
+ )
+
+ // See PredirectInstructions.tsx for the text we are verifying here
+ const exampleWindowHeader = screen.getByText('Test Bank').closest('.institution-panel-header')
+ expect(exampleWindowHeader).toBeInTheDocument()
+ expect(exampleWindowHeader).toHaveStyle({ backgroundColor: DEFAULT_HEADER_HEX_COLOR })
+ expect(screen.getByText('Log in at Test Bank', { selector: 'h2' })).toBeInTheDocument()
+
+ expect(
+ screen.getByText((content, element) => {
+ if (!element) return false
+ return (
+ element.classList.contains('predirect-instruction-text') &&
+ content.startsWith('To complete your connection, please') &&
+ element.textContent ===
+ 'To complete your connection, please share the following after signing in:'
+ )
+ }),
+ ).toBeInTheDocument()
+ })
+
+ it('an institution with predirect instructions can show the custom header color when it is provided from the API', async () => {
+ const onSignInClick = vi.fn()
+ const onAnalyticsEvent = vi.fn()
+ const customColor = '#ff0000'
+
+ const institution = {
+ guid: 'INS-test',
+ name: 'Test Bank',
+ oauth_predirect_instructions: [
+ OAUTH_PREDIRECT_INSTRUCTION.ACCOUNT_AND_TRANSACTIONS_INSTRUCTION,
+ OAUTH_PREDIRECT_INSTRUCTION.PROFILE_INFORMATION_INSTRUCTION,
+ ],
+ testProp: 'testValue',
+ brand_color_hex_code: customColor, // Custom red color for testing
+ }
+ const member = { guid: 'testGuid' }
+
+ const store = createTestReduxStore({
+ connect: {
+ isOauthLoading: false,
+ oauthURL: 'testUrl',
+ selectedInstitution: {
+ name: 'Test Bank',
+ },
+ },
+ })
+
+ const apiValue = {
+ oAuthStart: vi.fn(),
+ } as unknown as ApiContextTypes
+
+ render(
+ {}}
+ />,
+ {
+ apiValue,
+ onAnalyticsEvent,
+ store,
+ },
+ )
+
+ // See PredirectInstructions.tsx for the text we are verifying here
+ const exampleWindowHeader = screen.getByText('Test Bank').closest('.institution-panel-header')
+ expect(exampleWindowHeader).toBeInTheDocument()
+ expect(exampleWindowHeader).toHaveStyle({ backgroundColor: customColor })
+ expect(screen.getByText('Log in at Test Bank', { selector: 'h2' })).toBeInTheDocument()
+
+ expect(
+ screen.getByText((content, element) => {
+ if (!element) return false
+ return (
+ element.classList.contains('predirect-instruction-text') &&
+ content.startsWith('To complete your connection, please') &&
+ element.textContent ===
+ 'To complete your connection, please share the following after signing in:'
+ )
+ }),
+ ).toBeInTheDocument()
+ })
+
+ it('an institution that is configured with all bad values can show instructions for aggregation-only by default', async () => {
+ const onSignInClick = vi.fn()
+ const onAnalyticsEvent = vi.fn()
+
+ const institution = {
+ guid: 'INS-test',
+ name: 'Test Bank',
+ testProp: 'testValue',
+ oauth_predirect_instructions: [998, 999], // Invalid instruction value
+ }
+ const member = { guid: 'testGuid' }
+
+ const store = createTestReduxStore({
+ connect: {
+ isOauthLoading: false,
+ oauthURL: 'testUrl',
+ selectedInstitution: {
+ name: 'Test Bank',
+ },
+ },
+ })
+
+ const apiValue = {
+ oAuthStart: vi.fn(),
+ } as unknown as ApiContextTypes
+
+ render(
+ {}}
+ />,
+ {
+ apiValue,
+ onAnalyticsEvent,
+ store,
+ },
+ )
+
+ // See PredirectInstructions.tsx for the text we are verifying here
+ expect(screen.getByText('Test Bank')).toBeInTheDocument()
+ expect(screen.getByText('Log in at Test Bank', { selector: 'h2' })).toBeInTheDocument()
+
+ expect(
+ screen.getByText((content, element) => {
+ if (!element) return false
+ return (
+ element.classList.contains('predirect-instruction-text') &&
+ content.startsWith('To complete your connection, please') &&
+ element.textContent ===
+ 'To complete your connection, please share the following after signing in:'
+ )
+ }),
+ ).toBeInTheDocument()
+
+ expect(screen.getByText('Checking or savings account')).toBeInTheDocument()
})
})
diff --git a/src/views/oauth/experiments/__tests__/predirectInstructionUtils-test.ts b/src/views/oauth/experiments/__tests__/predirectInstructionUtils-test.ts
new file mode 100644
index 0000000000..af3c9e1d73
--- /dev/null
+++ b/src/views/oauth/experiments/__tests__/predirectInstructionUtils-test.ts
@@ -0,0 +1,115 @@
+import { describe, it, expect } from 'vitest'
+import {
+ isWellsFargoInstitution,
+ getInstitutionBrandColor,
+} from 'src/views/oauth/experiments/predirectInstructionsUtils'
+
+describe('predirectInstructionsUtils', () => {
+ describe('isWellsFargoInstitution', () => {
+ it('returns true for Wells Fargo PROD guid', () => {
+ const institution = {
+ guid: 'INS-6073ad01-da9e-f6ba-dfdf-5f1500d8e867',
+ } as InstitutionResponseType
+
+ expect(isWellsFargoInstitution(institution)).toBe(true)
+ })
+
+ it('returns true for Wells Fargo SAND guid', () => {
+ const institution = {
+ guid: 'INS-f9e8d5f6-b953-da63-32e4-6e88fbe8b250',
+ } as InstitutionResponseType
+
+ expect(isWellsFargoInstitution(institution)).toBe(true)
+ })
+
+ it('returns true for institution name Wells Fargo', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Wells Fargo',
+ } as InstitutionResponseType
+
+ expect(isWellsFargoInstitution(institution)).toBe(true)
+ })
+
+ it('returns false for non-Wells Fargo institution', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Chase Bank',
+ } as InstitutionResponseType
+
+ expect(isWellsFargoInstitution(institution)).toBe(false)
+ })
+ })
+
+ describe('getInstitutionBrandColor', () => {
+ it('returns configured color for Wells Fargo when brand_color_hex_code exists', () => {
+ const institution = {
+ guid: 'INS-6073ad01-da9e-f6ba-dfdf-5f1500d8e867',
+ brand_color_hex_code: '#D71E28',
+ } as InstitutionResponseType
+
+ expect(getInstitutionBrandColor(institution, '#000000')).toBe('#D71E28')
+ })
+
+ it('returns Wells Fargo red when brand_color_hex_code is missing', () => {
+ const institution = {
+ guid: 'INS-6073ad01-da9e-f6ba-dfdf-5f1500d8e867',
+ } as InstitutionResponseType
+
+ expect(getInstitutionBrandColor(institution, '#000000')).toBe('#B22222')
+ })
+
+ it('returns configured color for non-Wells Fargo institution', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Chase Bank',
+ brand_color_hex_code: '#117ACA',
+ } as InstitutionResponseType
+
+ expect(getInstitutionBrandColor(institution, '#000000')).toBe('#117ACA')
+ })
+
+ it('returns default color when brand_color_hex_code is missing for non-Wells Fargo', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Chase Bank',
+ } as InstitutionResponseType
+
+ expect(getInstitutionBrandColor(institution, '#AABBCC')).toBe('#AABBCC')
+ })
+
+ it('brand_color_hex_code: validates hex color format with 6 digits', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Test Bank',
+ brand_color_hex_code: '#1A2B3C',
+ } as InstitutionResponseType
+
+ const result = getInstitutionBrandColor(institution, '#000000')
+ expect(result).toMatch(/^#[0-9A-Fa-f]{6}$/)
+ expect(result).toBe('#1A2B3C')
+ })
+
+ it('brand_color_hex_code: validates hex color format with 8 digits (with transparency)', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Test Bank',
+ brand_color_hex_code: '#1A2B3C80',
+ } as InstitutionResponseType
+
+ const result = getInstitutionBrandColor(institution, '#00000000')
+ expect(result).toMatch(/^#[0-9A-Fa-f]{8}$/)
+ expect(result).toBe('#1A2B3C80')
+ })
+
+ it('brand_color_hex_code: returns default color when brand_color_hex_code is invalid', () => {
+ const institution = {
+ guid: 'INS-other-guid',
+ name: 'Test Bank',
+ brand_color_hex_code: 'invalid-color',
+ } as InstitutionResponseType
+
+ expect(getInstitutionBrandColor(institution, '#FFFFFF')).toBe('#FFFFFF')
+ })
+ })
+})
diff --git a/src/views/oauth/experiments/predirectInstructionsUtils.ts b/src/views/oauth/experiments/predirectInstructionsUtils.ts
new file mode 100644
index 0000000000..243463bbf9
--- /dev/null
+++ b/src/views/oauth/experiments/predirectInstructionsUtils.ts
@@ -0,0 +1,32 @@
+export function isWellsFargoInstitution(institution: InstitutionResponseType): boolean {
+ const wellsFargoGuids = [
+ 'INS-6073ad01-da9e-f6ba-dfdf-5f1500d8e867', // Wells Fargo PROD guid
+ 'INS-f9e8d5f6-b953-da63-32e4-6e88fbe8b250', // Wells Fargo SAND guid for testing
+ ]
+
+ return wellsFargoGuids.includes(institution.guid) || institution.name === 'Wells Fargo'
+}
+
+export function getInstitutionBrandColor(
+ institution: InstitutionResponseType,
+ defaultColor: string,
+): string {
+ const rawColor = institution?.brand_color_hex_code
+ const configuredInstitutionColor =
+ rawColor && /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6}|[0-9A-Fa-f]{8})$/.test(rawColor) ? rawColor : null
+
+ if (isWellsFargoInstitution(institution)) {
+ return configuredInstitutionColor || '#B22222' // Default Wells Fargo red
+ }
+
+ return configuredInstitutionColor || defaultColor
+}
+
+// These values are expected values from the backend
+export const OAUTH_PREDIRECT_INSTRUCTION = {
+ ACCOUNT_AND_TRANSACTIONS_INSTRUCTION: 0,
+ ACCOUNT_NUMBERS_INSTRUCTION: 1,
+ PROFILE_INFORMATION_INSTRUCTION: 2,
+ STATEMENTS_INSTRUCTION: 3,
+ TAX_INSTRUCTION: 4,
+}
diff --git a/typings/apiTypes.d.ts b/typings/apiTypes.d.ts
index 43e3870bae..30c1e4fcc3 100644
--- a/typings/apiTypes.d.ts
+++ b/typings/apiTypes.d.ts
@@ -124,6 +124,7 @@ type MemberResponseType = {
type InstitutionResponseType = {
account_verification_is_enabled: boolean
account_identification_is_enabled: boolean
+ brand_color_hex_code?: string | null
code: string
forgot_password_credential_recovery_url?: string | null
forgot_username_credential_recovery_url?: string | null
@@ -137,6 +138,7 @@ type InstitutionResponseType = {
is_disabled_by_client: boolean
login_url: string | null
name: string
+ oauth_predirect_instructions?: number[]
popularity?: number
supports_oauth: boolean
tax_statement_is_enabled: boolean
diff --git a/typings/mxTypes.d.ts b/typings/mxTypes.d.ts
index 2e52944217..f3c34fe12c 100644
--- a/typings/mxTypes.d.ts
+++ b/typings/mxTypes.d.ts
@@ -61,6 +61,7 @@ type MemberResponseType = {
type InstitutionResponseType = {
account_verification_is_enabled: boolean
account_identification_is_enabled: boolean
+ brand_color_hex_code?: string | null
code: string
forgot_password_credential_recovery_url?: string | null
forgot_username_credential_recovery_url?: string | null
@@ -74,6 +75,7 @@ type InstitutionResponseType = {
is_disabled_by_client: boolean
login_url: string | null
name: string
+ oauth_predirect_instructions?: number[]
popularity?: number
supports_oauth: boolean
tax_statement_is_enabled: boolean