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 */}
-
    +
      {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