diff --git a/__snapshots__/features/home/pages/Home.native.test.tsx.native-snap b/__snapshots__/features/home/pages/Home.native.test.tsx.native-snap
index d671533a4cc..0659e63a3af 100644
--- a/__snapshots__/features/home/pages/Home.native.test.tsx.native-snap
+++ b/__snapshots__/features/home/pages/Home.native.test.tsx.native-snap
@@ -373,7 +373,7 @@ exports[`Home page should render correctly 1`] = `
{
"busy": undefined,
"checked": undefined,
- "disabled": undefined,
+ "disabled": false,
"expanded": undefined,
"selected": undefined,
}
diff --git a/__snapshots__/features/profile/pages/Profile.native.test.tsx.native-snap b/__snapshots__/features/profile/pages/Profile.native.test.tsx.native-snap
index fe6ab0cff69..81d9a1cf55e 100644
--- a/__snapshots__/features/profile/pages/Profile.native.test.tsx.native-snap
+++ b/__snapshots__/features/profile/pages/Profile.native.test.tsx.native-snap
@@ -1765,7 +1765,7 @@ exports[`Profile component should render correctly 1`] = `
{
"busy": undefined,
"checked": undefined,
- "disabled": undefined,
+ "disabled": false,
"expanded": undefined,
"selected": undefined,
}
diff --git a/src/cheatcodes/pages/CheatcodesMenu.tsx b/src/cheatcodes/pages/CheatcodesMenu.tsx
index 3872260b8c0..b50f59b5174 100644
--- a/src/cheatcodes/pages/CheatcodesMenu.tsx
+++ b/src/cheatcodes/pages/CheatcodesMenu.tsx
@@ -54,6 +54,7 @@ export function CheatcodesMenu(): React.JSX.Element {
...cheatcodesNavigationTutorialButtons,
...cheatcodesNavigationForceUpdateButtons,
{ title: 'Share 🔗', screen: 'CheatcodesNavigationShare', subscreens: [] },
+ { title: 'RemoteBanner 🆒', screen: 'CheatcodesScreenRemoteBanner', subscreens: [] },
]
const otherButtons: CheatcodesButtonsWithSubscreensProps[] = [
diff --git a/src/cheatcodes/pages/features/remoteBanner/CheatcodesScreenRemoteBanner.tsx b/src/cheatcodes/pages/features/remoteBanner/CheatcodesScreenRemoteBanner.tsx
new file mode 100644
index 00000000000..5e94d01f561
--- /dev/null
+++ b/src/cheatcodes/pages/features/remoteBanner/CheatcodesScreenRemoteBanner.tsx
@@ -0,0 +1,36 @@
+import React, { useEffect, useState } from 'react'
+
+import { CheatcodesTemplateScreen } from 'cheatcodes/components/CheatcodesTemplateScreen'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
+import { remoteBannerSchema } from 'features/remoteBanner/components/remoteBannerSchema'
+import { useFeatureFlagOptions } from 'libs/firebase/firestore/featureFlags/useFeatureFlagOptions'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
+import { ErrorBanner } from 'ui/components/banners/ErrorBanner'
+import { ViewGap } from 'ui/components/ViewGap/ViewGap'
+import { getSpacing } from 'ui/theme'
+
+export const CheatcodesScreenRemoteBanner = () => {
+ const { options } = useFeatureFlagOptions(RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER)
+ const [error, setError] = useState('')
+
+ useEffect(() => {
+ try {
+ remoteBannerSchema.validateSync(options)
+ } catch (error) {
+ setError(String(error))
+ }
+ }, [options])
+
+ return (
+
+
+
+ {error ? (
+
+ ) : null}
+
+
+ )
+}
diff --git a/src/features/forceUpdate/components/ForceUpdateBanner.tsx b/src/features/forceUpdate/components/ForceUpdateBanner.tsx
deleted file mode 100644
index ddc80dd5589..00000000000
--- a/src/features/forceUpdate/components/ForceUpdateBanner.tsx
+++ /dev/null
@@ -1,23 +0,0 @@
-import React from 'react'
-import styled from 'styled-components/native'
-
-import { TITLE, BUTTON_TEXT_BANNER } from 'features/forceUpdate/constants'
-import { onPressStoreLink } from 'features/forceUpdate/helpers/onPressStoreLink'
-import { BannerWithBackground } from 'ui/components/ModuleBanner/BannerWithBackground'
-import { ArrowAgain } from 'ui/svg/icons/ArrowAgain'
-import { TypoDS } from 'ui/theme'
-
-export const ForceUpdateBanner = () => (
-
- {TITLE}
- {BUTTON_TEXT_BANNER}
-
-)
-
-const StyledButtonText = styled(TypoDS.Button)(({ theme }) => ({
- color: theme.colors.white,
-}))
-
-const StyledBodyText = styled(TypoDS.Body)(({ theme }) => ({
- color: theme.colors.white,
-}))
diff --git a/src/features/forceUpdate/constants.ts b/src/features/forceUpdate/constants.ts
index e580cbf28d4..c2259a32ae1 100644
--- a/src/features/forceUpdate/constants.ts
+++ b/src/features/forceUpdate/constants.ts
@@ -23,11 +23,6 @@ export const DESCRIPTION = Platform.select({
Pour des questions de performance et de sécurité merci d’actualiser la page pour obtenir la dernière version disponible.`,
})
-export const BUTTON_TEXT_BANNER = Platform.select({
- default: 'Télécharger la dernière version de l’application',
- web: 'Actualiser la page',
-})
-
export const BUTTON_TEXT_SCREEN = Platform.select({
default: 'Télécharger la dernière version',
web: 'Actualiser la page',
diff --git a/src/features/home/components/modules/banners/HomeBanner.native.test.tsx b/src/features/home/components/modules/banners/HomeBanner.native.test.tsx
index da4366ff37c..2b35f6b8100 100644
--- a/src/features/home/components/modules/banners/HomeBanner.native.test.tsx
+++ b/src/features/home/components/modules/banners/HomeBanner.native.test.tsx
@@ -26,26 +26,46 @@ const mockUseGeolocation = jest.mocked(useLocation)
jest.mock('shared/user/useGetDepositAmountsByAge')
const mockDepositAmounts = jest.mocked(useGetDepositAmountsByAge)
+jest.mock('@react-native-firebase/firestore')
+
+jest.useFakeTimers()
+
describe('', () => {
beforeEach(() => {
setFeatureFlags()
})
- it('should display force update banner when feature flag showForceUpdateBanner is enable', async () => {
- setFeatureFlags([RemoteStoreFeatureFlags.SHOW_FORCE_UPDATE_BANNER])
- mockSubscriptionStepper()
- mockBannerFromBackend({
- banner: {
- name: BannerName.retry_identity_check_banner,
- title: 'Retente ubble',
- text: 'pour débloquer ton crédit',
- },
+ describe('when feature flag showRemoteBanner is enable', () => {
+ beforeEach(() => {
+ setFeatureFlags([
+ {
+ featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER,
+ options: {
+ title: 'title 1',
+ subtitleMobile: 'subtitleMobile 1',
+ subtitleWeb: 'subtitleWeb 1',
+ redirectionUrl: 'https://www.test.fr',
+ redirectionType: 'store',
+ },
+ },
+ ])
})
- renderHomeBanner({})
- await act(async () => {})
+ it('should display force update banner', async () => {
+ mockSubscriptionStepper()
+ mockBannerFromBackend({
+ banner: {
+ name: BannerName.retry_identity_check_banner,
+ title: 'Retente ubble',
+ text: 'pour débloquer ton crédit',
+ },
+ })
+ renderHomeBanner({})
- expect(screen.getByText('Mise à jour requise !')).toBeOnTheScreen()
+ const banner = await screen.findByText('title 1')
+
+ expect(banner).toBeOnTheScreen()
+ })
})
describe('When wipAppV2SystemBlock feature flag deactivated', () => {
@@ -124,7 +144,7 @@ describe('', () => {
})
describe('When wipAppV2SystemBlock feature flag activated', () => {
- beforeAll(() => {
+ beforeEach(() => {
setFeatureFlags([RemoteStoreFeatureFlags.WIP_APP_V2_SYSTEM_BLOCK])
})
diff --git a/src/features/home/components/modules/banners/HomeBanner.tsx b/src/features/home/components/modules/banners/HomeBanner.tsx
index 9ded335c014..a2969e42d34 100644
--- a/src/features/home/components/modules/banners/HomeBanner.tsx
+++ b/src/features/home/components/modules/banners/HomeBanner.tsx
@@ -3,11 +3,11 @@ import React, { ComponentType, FunctionComponent, useCallback, useMemo } from 'r
import styled from 'styled-components/native'
import { BannerName } from 'api/gen'
-import { ForceUpdateBanner } from 'features/forceUpdate/components/ForceUpdateBanner'
import { useActivationBanner } from 'features/home/api/useActivationBanner'
import { ActivationBanner } from 'features/home/components/banners/ActivationBanner'
import { SignupBanner } from 'features/home/components/banners/SignupBanner'
import { StepperOrigin, UseNavigationType } from 'features/navigation/RootNavigator/types'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
import { useFeatureFlag } from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
import { SystemBanner as GenericSystemBanner } from 'ui/components/ModuleBanner/SystemBanner'
@@ -54,7 +54,7 @@ const bannersToRender = [
]
export const HomeBanner = ({ isLoggedIn }: HomeBannerProps) => {
- const showForceUpdateBanner = useFeatureFlag(RemoteStoreFeatureFlags.SHOW_FORCE_UPDATE_BANNER)
+ const showRemoteBanner = useFeatureFlag(RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER)
const { banner } = useActivationBanner()
const { navigate } = useNavigation()
const enableSystemBanner = useFeatureFlag(RemoteStoreFeatureFlags.WIP_APP_V2_SYSTEM_BLOCK)
@@ -138,10 +138,10 @@ export const HomeBanner = ({ isLoggedIn }: HomeBannerProps) => {
return null
}, [isLoggedIn, shouldRenderSystemBanner, renderSystemBanner, banner, enableSystemBanner])
- if (showForceUpdateBanner) {
+ if (showRemoteBanner) {
return (
-
+
)
}
diff --git a/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigator.tsx b/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigator.tsx
index 2051c0781a5..567e4b562e5 100644
--- a/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigator.tsx
+++ b/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigator.tsx
@@ -14,6 +14,7 @@ import { CheatcodesNavigationIdentityCheck } from 'cheatcodes/pages/features/ide
import { CheatcodesNavigationNewIdentificationFlow } from 'cheatcodes/pages/features/identityCheck/CheatcodesNavigationNewIdentificationFlow'
import { CheatcodesNavigationInternal } from 'cheatcodes/pages/features/internal/CheatcodesNavigationInternal'
import { CheatcodesNavigationProfile } from 'cheatcodes/pages/features/profile/CheatcodesNavigationProfile'
+import { CheatcodesScreenRemoteBanner } from 'cheatcodes/pages/features/remoteBanner/CheatcodesScreenRemoteBanner'
import { CheatcodesNavigationShare } from 'cheatcodes/pages/features/share/CheatcodesNavigationShare'
import { CheatcodesNavigationSubscription } from 'cheatcodes/pages/features/subscription/CheatcodesNavigationSubscription'
import { CheatcodesNavigationTrustedDevice } from 'cheatcodes/pages/features/trustedDevice/CheatcodesNavigationTrustedDevice'
@@ -137,6 +138,10 @@ const routes: CheatcodesStackRoute[] = [
name: 'CheatcodesScreenNewCaledonia',
component: CheatcodesScreenNewCaledonia,
},
+ {
+ name: 'CheatcodesScreenRemoteBanner',
+ component: CheatcodesScreenRemoteBanner,
+ },
{
name: 'CheatcodesNavigationErrors',
component: withAsyncErrorBoundary(CheatcodesNavigationErrors),
diff --git a/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigatorConfig.tsx b/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigatorConfig.tsx
index d591bb95bb3..e5f8d0b47ec 100644
--- a/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigatorConfig.tsx
+++ b/src/features/navigation/CheatcodesStackNavigator/CheatcodesStackNavigatorConfig.tsx
@@ -3,35 +3,36 @@ export const cheatcodesStackNavigatorConfig = {
initialRouteName: 'cheatcodes',
screens: {
CheatcodesMenu: 'cheatcodes',
- CheatcodesNavigationHome: 'cheatcodes/home',
- CheatcodesScreenCategoryThematicHomeHeader: 'cheatcodes/home/category-thematic-home-header',
- CheatcodesScreenDefaultThematicHomeHeader: 'cheatcodes/home/default-thematic-home-header',
- CheatcodesScreenHighlightThematicHomeHeader: 'cheatcodes/home/highlight-thematic-home-header',
- CheatcodesNavigationProfile: 'cheatcodes/profile',
+ CheatcodesNavigationAccountManagement: 'cheatcodes/other/account-management',
CheatcodesNavigationAchievements: 'cheatcodes/achievements',
- CheatcodesNavigationShare: 'cheatcodes/share',
- CheatcodesNavigationSubscription: 'cheatcodes/subscription',
+ CheatcodesNavigationBookOffer: 'cheatcodes/book-offer',
CheatcodesNavigationCulturalSurvey: 'cheatcodes/cultural-survey',
- CheatcodesNavigationTutorial: 'cheatcodes/tutorial',
- CheatcodesNavigationOnboarding: 'cheatcodes/tutorial/onboarding',
- CheatcodesNavigationProfileTutorial: 'cheatcodes/tutorial/profile-tutorial',
- CheatcodesNavigationTrustedDevice: 'cheatcodes/trusted-device',
- CheatcodesScreenTrustedDeviceInfos: 'cheatcodes/trusted-device/trusted-device-infos',
+ CheatcodesNavigationErrors: 'cheatcodes/other/errors',
+ CheatcodesNavigationForceUpdate: 'cheatcodes/other/force-update',
+ CheatcodesNavigationHome: 'cheatcodes/home',
CheatcodesNavigationIdentityCheck: 'cheatcodes/identity-check',
+ CheatcodesNavigationInternal: 'cheatcodes/internal',
CheatcodesNavigationNewIdentificationFlow:
'cheatcodes/identity-check/new-identification-flow',
- CheatcodesNavigationInternal: 'cheatcodes/internal',
- CheatcodesNavigationBookOffer: 'cheatcodes/book-offer',
+ CheatcodesNavigationNotScreensPages: 'cheatcodes/other/not-screens-pages',
+ CheatcodesNavigationOnboarding: 'cheatcodes/tutorial/onboarding',
+ CheatcodesNavigationProfile: 'cheatcodes/profile',
+ CheatcodesNavigationProfileTutorial: 'cheatcodes/tutorial/profile-tutorial',
+ CheatcodesNavigationShare: 'cheatcodes/share',
+ CheatcodesNavigationSignUp: 'cheatcodes/other/sign-up',
+ CheatcodesNavigationSubscription: 'cheatcodes/subscription',
+ CheatcodesNavigationTrustedDevice: 'cheatcodes/trusted-device',
+ CheatcodesNavigationTutorial: 'cheatcodes/tutorial',
+ CheatcodesScreenAccesLibre: 'cheatcodes/other/acces-libre',
+ CheatcodesScreenCategoryThematicHomeHeader: 'cheatcodes/home/category-thematic-home-header',
CheatcodesScreenDebugInformations: 'cheatcodes/other/debug-informations',
+ CheatcodesScreenDefaultThematicHomeHeader: 'cheatcodes/home/default-thematic-home-header',
CheatcodesScreenFeatureFlags: 'cheatcodes/other/feature-flags',
- CheatcodesScreenRemoteConfig: 'cheatcodes/other/remote-config',
+ CheatcodesScreenHighlightThematicHomeHeader: 'cheatcodes/home/highlight-thematic-home-header',
CheatcodesScreenNewCaledonia: 'cheatcodes/other/new-caledonia',
- CheatcodesNavigationErrors: 'cheatcodes/other/errors',
- CheatcodesNavigationNotScreensPages: 'cheatcodes/other/not-screens-pages',
- CheatcodesScreenAccesLibre: 'cheatcodes/other/acces-libre',
- CheatcodesNavigationSignUp: 'cheatcodes/other/sign-up',
- CheatcodesNavigationAccountManagement: 'cheatcodes/other/account-management',
- CheatcodesNavigationForceUpdate: 'cheatcodes/other/force-update',
+ CheatcodesScreenRemoteBanner: 'cheatcodes/remote-banner',
+ CheatcodesScreenRemoteConfig: 'cheatcodes/other/remote-config',
+ CheatcodesScreenTrustedDeviceInfos: 'cheatcodes/trusted-device/trusted-device-infos',
},
},
}
diff --git a/src/features/navigation/CheatcodesStackNavigator/types.ts b/src/features/navigation/CheatcodesStackNavigator/types.ts
index 40041a9c965..59fb7af9f83 100644
--- a/src/features/navigation/CheatcodesStackNavigator/types.ts
+++ b/src/features/navigation/CheatcodesStackNavigator/types.ts
@@ -20,6 +20,7 @@ export type CheatcodesStackParamList = {
CheatcodesScreenCategoryThematicHomeHeader: undefined
CheatcodesScreenDefaultThematicHomeHeader: undefined
CheatcodesScreenHighlightThematicHomeHeader: undefined
+ CheatcodesScreenRemoteBanner: undefined
CheatcodesScreenTrustedDeviceInfos: undefined
// Others
CheatcodesScreenDebugInformations: undefined
diff --git a/src/features/profile/components/Badges/SubscriptionMessageBadge.tsx b/src/features/profile/components/Badges/SubscriptionMessageBadge.tsx
index e96c90f281d..8ed5ebc9f54 100644
--- a/src/features/profile/components/Badges/SubscriptionMessageBadge.tsx
+++ b/src/features/profile/components/Badges/SubscriptionMessageBadge.tsx
@@ -3,11 +3,11 @@ import { openInbox } from 'react-native-email-link'
import { SubscriptionMessage } from 'api/gen'
import { useIsMailAppAvailable } from 'features/auth/helpers/useIsMailAppAvailable'
-import { ForceUpdateBanner } from 'features/forceUpdate/components/ForceUpdateBanner'
import { Subtitle } from 'features/profile/components/Subtitle/Subtitle'
import { formatDateToLastUpdatedAtMessage } from 'features/profile/helpers/formatDateToLastUpdatedAtMessage'
import { matchSubscriptionMessageIconToSvg } from 'features/profile/helpers/matchSubscriptionMessageIconToSvg'
import { shouldOpenInbox as checkShouldOpenInbox } from 'features/profile/helpers/shouldOpenInbox'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
import { InfoBanner } from 'ui/components/banners/InfoBanner'
import { BaseButtonProps } from 'ui/components/buttons/AppButton/types'
import { ButtonQuaternarySecondary } from 'ui/components/buttons/ButtonQuaternarySecondary'
@@ -88,7 +88,7 @@ export const SubscriptionMessageBadge = ({
{disableActivation ? (
-
+
) : null}
diff --git a/src/features/profile/components/Header/CreditHeader/CreditHeader.native.test.tsx b/src/features/profile/components/Header/CreditHeader/CreditHeader.native.test.tsx
index 93d87ec6618..975e341b561 100644
--- a/src/features/profile/components/Header/CreditHeader/CreditHeader.native.test.tsx
+++ b/src/features/profile/components/Header/CreditHeader/CreditHeader.native.test.tsx
@@ -219,7 +219,7 @@ describe('CreditHeader', () => {
const renderCreditHeader = (props?: Partial) => {
render(
}
diff --git a/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.stories.tsx b/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.stories.tsx
index 0bcc29582d7..c60fecede36 100644
--- a/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.stories.tsx
+++ b/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.stories.tsx
@@ -28,19 +28,19 @@ const Template: ComponentStory = (props) => (
export const WithActivationBanner = Template.bind({})
WithActivationBanner.args = {
- showForceUpdateBanner: true,
+ showRemoteBanner: true,
title: 'Jean Dubois',
}
export const WithTitle = Template.bind({})
WithTitle.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
}
export const WithStringSubtitle = Template.bind({})
WithStringSubtitle.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
subtitle: 'Tu as entre 15 et 18 ans\u00a0?',
}
@@ -52,7 +52,7 @@ const Row = styled.View({
export const WithComponentAsSubtitle = Template.bind({})
WithComponentAsSubtitle.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
subtitle: (
@@ -66,14 +66,14 @@ WithComponentAsSubtitle.args = {
export const WithInfoBanner = Template.bind({})
WithInfoBanner.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
bannerText: 'Some really important information',
}
export const WithLargeContent = Template.bind({})
WithLargeContent.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
subtitle: 'Tu as entre 15 et 18 ans\u00a0?',
children: (
@@ -87,7 +87,7 @@ WithLargeContent.args = {
export const WithSmallContent = Template.bind({})
WithSmallContent.args = {
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
title: 'Jean Dubois',
subtitle: 'Tu as entre 15 et 18 ans\u00a0?',
children: Lorem ipsum dolor, sit amet consectetur,
diff --git a/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.tsx b/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.tsx
index cae1b599d8b..dfc451544e8 100644
--- a/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.tsx
+++ b/src/features/profile/components/Header/HeaderWithGreyContainer/HeaderWithGreyContainer.tsx
@@ -1,14 +1,14 @@
import React, { FunctionComponent, ReactNode } from 'react'
import styled from 'styled-components/native'
-import { ForceUpdateBanner } from 'features/forceUpdate/components/ForceUpdateBanner'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
import { InfoBanner } from 'ui/components/banners/InfoBanner'
import { PageHeader } from 'ui/components/headers/PageHeader'
import { Info } from 'ui/svg/icons/Info'
import { getSpacing, Spacer, TypoDS } from 'ui/theme'
type PropsWithChildren = {
- showForceUpdateBanner: boolean
+ showRemoteBanner: boolean
title: string
subtitle?: ReactNode | string
withGreyContainer?: boolean
@@ -17,7 +17,7 @@ type PropsWithChildren = {
}
export const HeaderWithGreyContainer: FunctionComponent = ({
- showForceUpdateBanner,
+ showRemoteBanner,
title,
subtitle,
bannerText,
@@ -35,9 +35,9 @@ export const HeaderWithGreyContainer: FunctionComponent = ({
) : (
)}
- {showForceUpdateBanner ? (
+ {showRemoteBanner ? (
-
+
) : null}
diff --git a/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.native.test.tsx b/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.native.test.tsx
index 6c0d0c55d72..192cc590114 100644
--- a/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.native.test.tsx
+++ b/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.native.test.tsx
@@ -19,7 +19,7 @@ describe('LoggedOutHeader', () => {
beforeEach(() => setFeatureFlags())
it('should display subtitle with credit V2', () => {
- render()
+ render()
const subtitle = 'Tu as entre 15 et 18 ans\u00a0?'
@@ -27,7 +27,7 @@ describe('LoggedOutHeader', () => {
})
it('should navigate to the SignupForm page', async () => {
- render()
+ render()
const signupButton = screen.getByText('Créer un compte')
await user.press(signupButton)
@@ -38,7 +38,7 @@ describe('LoggedOutHeader', () => {
})
it('should navigate to the Login page', async () => {
- render()
+ render()
const signinButton = screen.getByText('Se connecter')
await user.press(signinButton)
@@ -49,7 +49,7 @@ describe('LoggedOutHeader', () => {
})
it('should log analytics when clicking on "Créer un compte"', async () => {
- render()
+ render()
const signupButton = screen.getByText('Créer un compte')
await user.press(signupButton)
@@ -64,7 +64,7 @@ describe('LoggedOutHeader', () => {
})
it('should display subtitle with credit V3', () => {
- render()
+ render()
const subtitle = 'Tu as 17 ou 18 ans\u00a0?'
@@ -74,7 +74,7 @@ describe('LoggedOutHeader', () => {
it('should not display subtitle with passForAll enabled', () => {
setFeatureFlags([RemoteStoreFeatureFlags.ENABLE_PASS_FOR_ALL])
- render()
+ render()
const subtitle = 'Tu as 17 ou 18 ans\u00a0?'
diff --git a/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.tsx b/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.tsx
index 411fce611a3..79eadfd3846 100644
--- a/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.tsx
+++ b/src/features/profile/components/Header/LoggedOutHeader/LoggedOutHeader.tsx
@@ -13,7 +13,7 @@ import { InternalTouchableLink } from 'ui/components/touchableLink/InternalTouch
import { getSpacing, Spacer, TypoDS } from 'ui/theme'
type Props = {
- showForceUpdateBanner: boolean
+ showRemoteBanner: boolean
}
const onBeforeNavigate = () => {
@@ -21,7 +21,7 @@ const onBeforeNavigate = () => {
analytics.logSignUpClicked({ from: 'profile' })
}
-export const LoggedOutHeader: FunctionComponent = ({ showForceUpdateBanner }) => {
+export const LoggedOutHeader: FunctionComponent = ({ showRemoteBanner }) => {
const isPassForAllEnabled = useFeatureFlag(RemoteStoreFeatureFlags.ENABLE_PASS_FOR_ALL)
const { data: settings } = useSettingsContext()
const enableCreditV3 = settings?.wipEnableCreditV3
@@ -32,7 +32,7 @@ export const LoggedOutHeader: FunctionComponent = ({ showForceUpdateBanne
return (
{bodyText}
diff --git a/src/features/profile/components/Header/NonBeneficiaryHeader/NonBeneficiaryHeader.tsx b/src/features/profile/components/Header/NonBeneficiaryHeader/NonBeneficiaryHeader.tsx
index 8888ef3a85b..8df82f66982 100644
--- a/src/features/profile/components/Header/NonBeneficiaryHeader/NonBeneficiaryHeader.tsx
+++ b/src/features/profile/components/Header/NonBeneficiaryHeader/NonBeneficiaryHeader.tsx
@@ -3,7 +3,6 @@ import React, { FunctionComponent, memo, PropsWithChildren } from 'react'
import styled from 'styled-components/native'
import { Banner, BannerName } from 'api/gen'
-import { ForceUpdateBanner } from 'features/forceUpdate/components/ForceUpdateBanner'
import { useActivationBanner } from 'features/home/api/useActivationBanner'
import { ActivationBanner } from 'features/home/components/banners/ActivationBanner'
import { useGetStepperInfo } from 'features/identityCheck/api/useGetStepperInfo'
@@ -12,6 +11,7 @@ import { IdentityCheckPendingBadge } from 'features/profile/components/Badges/Id
import { SubscriptionMessageBadge } from 'features/profile/components/Badges/SubscriptionMessageBadge'
import { YoungerBadge } from 'features/profile/components/Badges/YoungerBadge'
import { EligibilityMessage } from 'features/profile/components/Header/NonBeneficiaryHeader/EligibilityMessage'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
import { formatToSlashedFrenchDate } from 'libs/dates'
import { PageHeader } from 'ui/components/headers/PageHeader'
import { SystemBanner as GenericSystemBanner } from 'ui/components/ModuleBanner/SystemBanner'
@@ -163,7 +163,7 @@ function NonBeneficiaryBanner({
return (
-
+
)
}
diff --git a/src/features/profile/components/Header/ProfileHeader/ProfileHeader.native.test.tsx b/src/features/profile/components/Header/ProfileHeader/ProfileHeader.native.test.tsx
index fb48a9be73a..665494dec82 100644
--- a/src/features/profile/components/Header/ProfileHeader/ProfileHeader.native.test.tsx
+++ b/src/features/profile/components/Header/ProfileHeader/ProfileHeader.native.test.tsx
@@ -88,7 +88,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: true,
},
user: undefined,
@@ -105,7 +105,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user: undefined,
@@ -124,7 +124,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user,
@@ -140,7 +140,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user,
@@ -155,7 +155,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user: exBeneficiaryUser,
@@ -178,7 +178,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user: notBeneficiaryUser,
@@ -194,7 +194,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: false,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
},
user: exUnderageBeneficiaryUser,
@@ -212,7 +212,7 @@ const renderProfileHeader = ({
enableAchievements: boolean
enableSystemBanner: boolean
disableActivation: boolean
- showForceUpdateBanner: boolean
+ showRemoteBanner: boolean
enablePassForAll: boolean
}
user?: UserProfileResponse
diff --git a/src/features/profile/components/Header/ProfileHeader/ProfileHeader.tsx b/src/features/profile/components/Header/ProfileHeader/ProfileHeader.tsx
index d2f6009b162..985da33d708 100644
--- a/src/features/profile/components/Header/ProfileHeader/ProfileHeader.tsx
+++ b/src/features/profile/components/Header/ProfileHeader/ProfileHeader.tsx
@@ -17,7 +17,7 @@ type ProfileHeaderProps = {
enableAchievements: boolean
enableSystemBanner: boolean
disableActivation: boolean
- showForceUpdateBanner: boolean
+ showRemoteBanner: boolean
enablePassForAll: boolean
}
user?: UserProfileResponse
@@ -33,7 +33,7 @@ export function ProfileHeader(props: ProfileHeaderProps) {
const ProfileHeader = useMemo(() => {
if (!isLoggedIn || !user) {
- return
+ return
}
if (!user.isBeneficiary || user.isEligibleForBeneficiaryUpgrade) {
@@ -49,7 +49,7 @@ export function ProfileHeader(props: ProfileHeaderProps) {
return (
{
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
}}
user={user}
@@ -76,7 +76,7 @@ describe('ProfileHeader', () => {
enableAchievements: false,
enableSystemBanner: true,
disableActivation: false,
- showForceUpdateBanner: false,
+ showRemoteBanner: false,
enablePassForAll: false,
}}
user={exBeneficiaryUser}
diff --git a/src/features/profile/pages/Profile.native.test.tsx b/src/features/profile/pages/Profile.native.test.tsx
index 9c1bb3bddda..c33b6f3d3a5 100644
--- a/src/features/profile/pages/Profile.native.test.tsx
+++ b/src/features/profile/pages/Profile.native.test.tsx
@@ -38,6 +38,7 @@ import {
render,
screen,
userEvent,
+ waitFor,
} from 'tests/utils'
import * as useVersion from 'ui/hooks/useVersion'
@@ -164,18 +165,24 @@ describe('Profile component', () => {
setFeatureFlags([RemoteStoreFeatureFlags.ENABLE_ACHIEVEMENTS])
})
- it('should show banner when FF is enabled and user is a beneficiary', () => {
+ it('should show banner when FF is enabled and user is a beneficiary', async () => {
mockedUseAuthContext.mockReturnValueOnce({ user: beneficiaryUser })
renderProfile()
- expect(screen.getByText('Mes succès')).toBeOnTheScreen()
+ await waitFor(() => {
+ // this banner is not shown if the force update banner is shown (which needs to wait for firestore, thus the achievement banner must wait for firestore as well).
+ expect(screen.getByText('Mes succès')).toBeOnTheScreen()
+ })
})
it('should not show banner if user is not a beneficiary', async () => {
renderProfile()
await screen.findByText('Mon profil')
- expect(screen.queryByText('Mes succès')).not.toBeOnTheScreen()
+ await waitFor(() => {
+ // this banner is not shown if the force update banner is shown (which needs to wait for firestore, thus the achievement banner must wait for firestore as well).
+ expect(screen.queryByText('Mes succès')).not.toBeOnTheScreen()
+ })
})
it('should not show banner when FF is disabled', async () => {
diff --git a/src/features/profile/pages/Profile.tsx b/src/features/profile/pages/Profile.tsx
index 71114c0aeda..58bc3c8de7e 100644
--- a/src/features/profile/pages/Profile.tsx
+++ b/src/features/profile/pages/Profile.tsx
@@ -60,7 +60,7 @@ const OnlineProfile: React.FC = () => {
const enableAchievements = useFeatureFlag(RemoteStoreFeatureFlags.ENABLE_ACHIEVEMENTS)
const enableSystemBanner = useFeatureFlag(RemoteStoreFeatureFlags.WIP_APP_V2_SYSTEM_BLOCK)
const disableActivation = useFeatureFlag(RemoteStoreFeatureFlags.DISABLE_ACTIVATION)
- const showForceUpdateBanner = useFeatureFlag(RemoteStoreFeatureFlags.SHOW_FORCE_UPDATE_BANNER)
+ const showRemoteBanner = useFeatureFlag(RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER)
const enablePassForAll = useFeatureFlag(RemoteStoreFeatureFlags.ENABLE_PASS_FOR_ALL)
const { dispatch: favoritesDispatch } = useFavoritesState()
@@ -171,7 +171,7 @@ const OnlineProfile: React.FC = () => {
enableAchievements,
enableSystemBanner,
disableActivation,
- showForceUpdateBanner,
+ showRemoteBanner,
enablePassForAll,
}}
user={user}
diff --git a/src/features/remoteBanner/components/RemoteBanner.native.test.tsx b/src/features/remoteBanner/components/RemoteBanner.native.test.tsx
new file mode 100644
index 00000000000..91ca03b34b2
--- /dev/null
+++ b/src/features/remoteBanner/components/RemoteBanner.native.test.tsx
@@ -0,0 +1,173 @@
+import React from 'react'
+
+import * as NavigationHelpers from 'features/navigation/helpers/openUrl'
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
+import {
+ RemoteBannerRedirectionType,
+ RemoteBannerType,
+} from 'features/remoteBanner/components/remoteBannerSchema'
+import { analytics } from 'libs/analytics/provider'
+import { setFeatureFlags } from 'libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
+import { eventMonitoring } from 'libs/monitoring/services'
+import { render, screen, userEvent } from 'tests/utils'
+
+jest.mock('libs/firebase/analytics/analytics')
+
+const openUrl = jest.spyOn(NavigationHelpers, 'openUrl')
+
+jest.useFakeTimers()
+const user = userEvent.setup()
+const appStoreUrl = 'https://apps.apple.com/fr/app/pass-culture/id1557887412'
+
+describe('RemoteBanner', () => {
+ it('when the showRemoteBanner FF is off, the banner should not be displayed', () => {
+ setFeatureFlags()
+ render()
+
+ const banner = screen.queryByText('title 1')
+
+ expect(banner).not.toBeOnTheScreen()
+ })
+
+ it('when redirection type is an expected value, banner should appear', () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerExternalUrl },
+ ])
+ render()
+
+ const banner = screen.queryByText('title 1')
+
+ expect(banner).toBeOnTheScreen()
+ })
+
+ it('when redirection type is an unexpected value, banner should not appear', () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerBadType },
+ ])
+ render()
+
+ const banner = screen.queryByText('title 1')
+
+ expect(banner).not.toBeOnTheScreen()
+ })
+
+ it('when redirection type is an unexpected value, should log to sentry', () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerBadType },
+ ])
+ render()
+
+ expect(eventMonitoring.captureException).toHaveBeenCalledWith(
+ new Error(
+ 'RemoteBanner validation issue: ValidationError: redirectionType must be one of the following values: external, store'
+ ),
+ {
+ extra: {
+ objectToValidate: {
+ ...bannerBadType,
+ },
+ },
+ }
+ )
+ })
+
+ it('when redirection is to app store, should navigate to store and a11y label should be correct', async () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerAppStore },
+ ])
+ render()
+
+ const banner = await screen.findByText('title 1')
+ await user.press(banner)
+
+ const accessibilityLabel = await screen.findByLabelText(`Nouvelle fenêtre : ${appStoreUrl}`)
+
+ expect(accessibilityLabel).toBeTruthy()
+ expect(openUrl).toHaveBeenCalledWith(appStoreUrl)
+ })
+
+ it('when redirection is external, should navigate to url and a11y label should be correct', async () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerExternalUrl },
+ ])
+ render()
+
+ const banner = await screen.findByText('title 1')
+ await user.press(banner)
+
+ const accessibilityLabel = await screen.findByLabelText(
+ 'Nouvelle fenêtre : https://www.test.fr'
+ )
+
+ expect(accessibilityLabel).toBeTruthy()
+ expect(openUrl).toHaveBeenCalledWith('https://www.test.fr')
+ })
+
+ it('when redirection is external, but url is an empty string, button should be disabled and there should not be an a11y label', async () => {
+ setFeatureFlags([
+ {
+ featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER,
+ options: bannerExternalUrlWithMissingUrl,
+ },
+ ])
+ render()
+
+ const banner = await screen.findByText('title 1')
+ await user.press(banner)
+
+ const accessibilityLabel = screen.queryByLabelText('Nouvelle fenêtre : https://www.test.fr')
+
+ expect(accessibilityLabel).toBeFalsy()
+ expect(openUrl).not.toHaveBeenCalled()
+ })
+
+ it('when user presses banner, should log analytics', async () => {
+ setFeatureFlags([
+ {
+ featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER,
+ options: bannerAppStore,
+ },
+ ])
+ render()
+
+ const banner = await screen.findByText('title 1')
+ await user.press(banner)
+
+ expect(analytics.logHasClickedRemoteBanner).toHaveBeenCalledWith('Profile', {
+ ...bannerAppStore,
+ })
+ })
+})
+
+const bannerAppStore: RemoteBannerType = {
+ title: 'title 1',
+ subtitleMobile: 'subtitleMobile 1',
+ subtitleWeb: 'subtitleWeb 1',
+ redirectionUrl: 'https://www.test.fr',
+ redirectionType: RemoteBannerRedirectionType.STORE,
+}
+
+const bannerExternalUrl: RemoteBannerType = {
+ title: 'title 1',
+ subtitleMobile: 'subtitleMobile 1',
+ subtitleWeb: 'subtitleWeb 1',
+ redirectionUrl: 'https://www.test.fr',
+ redirectionType: RemoteBannerRedirectionType.EXTERNAL,
+}
+
+const bannerExternalUrlWithMissingUrl: Partial = {
+ title: 'title 1',
+ subtitleMobile: 'subtitleMobile 1',
+ subtitleWeb: 'subtitleWeb 1',
+ redirectionUrl: '',
+ redirectionType: RemoteBannerRedirectionType.EXTERNAL,
+}
+
+const bannerBadType: Partial = {
+ title: 'title 1',
+ subtitleMobile: 'subtitleMobile 1',
+ subtitleWeb: 'subtitleWeb 1',
+ redirectionUrl: 'https://www.test.fr',
+ redirectionType: 'other',
+}
diff --git a/src/features/remoteBanner/components/RemoteBanner.tsx b/src/features/remoteBanner/components/RemoteBanner.tsx
new file mode 100644
index 00000000000..0315a011584
--- /dev/null
+++ b/src/features/remoteBanner/components/RemoteBanner.tsx
@@ -0,0 +1,63 @@
+import React from 'react'
+import { Platform } from 'react-native'
+import styled from 'styled-components/native'
+
+import { STORE_LINK } from 'features/forceUpdate/constants'
+import { onPressStoreLink } from 'features/forceUpdate/helpers/onPressStoreLink'
+import { openUrl } from 'features/navigation/helpers/openUrl'
+import {
+ RemoteBannerRedirectionType,
+ validateRemoteBanner,
+} from 'features/remoteBanner/components/remoteBannerSchema'
+import { accessibilityAndTestId } from 'libs/accessibilityAndTestId'
+import { analytics } from 'libs/analytics/provider'
+import { useFeatureFlagOptions } from 'libs/firebase/firestore/featureFlags/useFeatureFlagOptions'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
+import { BannerWithBackground } from 'ui/components/ModuleBanner/BannerWithBackground'
+import { ArrowAgain } from 'ui/svg/icons/ArrowAgain'
+import { TypoDS } from 'ui/theme'
+
+const isWeb = Platform.OS === 'web'
+
+export type RemoteBannerOrigin = 'Profile' | 'Home' | 'Cheatcodes'
+
+export const RemoteBanner = ({ from }: { from: RemoteBannerOrigin }) => {
+ const { options } = useFeatureFlagOptions(RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER)
+ const validatedOptions = validateRemoteBanner(options)
+ if (!validatedOptions) return null
+ const { title, subtitleMobile, subtitleWeb, redirectionUrl, redirectionType } = validatedOptions
+
+ const isStoreRedirection = redirectionType === RemoteBannerRedirectionType.STORE
+ const isExternalRedirection = redirectionType === RemoteBannerRedirectionType.EXTERNAL
+ const isExternalAndDefined = isExternalRedirection && redirectionUrl
+
+ const storeAccessibilityLabel = isStoreRedirection ? `Nouvelle fenêtre\u00a0: ${STORE_LINK}` : ''
+ const accessibilityLabel = isExternalAndDefined
+ ? `Nouvelle fenêtre\u00a0: ${String(redirectionUrl)}`
+ : storeAccessibilityLabel
+
+ const onPress = () => {
+ analytics.logHasClickedRemoteBanner(from, validatedOptions)
+ if (isStoreRedirection) onPressStoreLink()
+ if (isExternalAndDefined) openUrl(redirectionUrl)
+ }
+
+ return (
+
+ {title}
+ {isWeb ? subtitleWeb : subtitleMobile}
+
+ )
+}
+
+const StyledButtonText = styled(TypoDS.Button)(({ theme }) => ({
+ color: theme.colors.white,
+}))
+
+const StyledBodyText = styled(TypoDS.Body)(({ theme }) => ({
+ color: theme.colors.white,
+}))
diff --git a/src/features/remoteBanner/components/RemoteBanner.web.test.tsx b/src/features/remoteBanner/components/RemoteBanner.web.test.tsx
new file mode 100644
index 00000000000..a58978b59ca
--- /dev/null
+++ b/src/features/remoteBanner/components/RemoteBanner.web.test.tsx
@@ -0,0 +1,45 @@
+import React from 'react'
+
+import { RemoteBanner } from 'features/remoteBanner/components/RemoteBanner'
+import {
+ RemoteBannerRedirectionType,
+ RemoteBannerType,
+} from 'features/remoteBanner/components/remoteBannerSchema'
+import { setFeatureFlags } from 'libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
+import { render, screen } from 'tests/utils/web'
+
+jest.mock('libs/firebase/analytics/analytics')
+jest.mock('libs/firebase/remoteConfig/remoteConfig.services')
+
+describe('RemoteBanner', () => {
+ it('should show web specific subtitle', async () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerExternalUrl },
+ ])
+ render()
+
+ const subtitle = screen.queryByText('subtitleWeb')
+
+ expect(subtitle).toBeInTheDocument()
+ })
+
+ it('should not show mobile specific subtitle', async () => {
+ setFeatureFlags([
+ { featureFlag: RemoteStoreFeatureFlags.SHOW_REMOTE_BANNER, options: bannerExternalUrl },
+ ])
+ render()
+
+ const subtitle = screen.queryByText('subtitleMobile')
+
+ expect(subtitle).not.toBeInTheDocument()
+ })
+})
+
+const bannerExternalUrl: RemoteBannerType = {
+ title: 'title',
+ subtitleMobile: 'subtitleMobile',
+ subtitleWeb: 'subtitleWeb',
+ redirectionUrl: 'https://www.test.fr',
+ redirectionType: RemoteBannerRedirectionType.EXTERNAL,
+}
diff --git a/src/features/remoteBanner/components/remoteBannerSchema.ts b/src/features/remoteBanner/components/remoteBannerSchema.ts
new file mode 100644
index 00000000000..1340ea48c54
--- /dev/null
+++ b/src/features/remoteBanner/components/remoteBannerSchema.ts
@@ -0,0 +1,29 @@
+import { InferType, object, string } from 'yup'
+
+import { eventMonitoring } from 'libs/monitoring/services'
+
+export const remoteBannerSchema = object({
+ title: string().required(),
+ subtitleWeb: string().nullable(),
+ subtitleMobile: string().nullable(),
+ redirectionUrl: string().url().nullable(),
+ redirectionType: string().oneOf(['external', 'store']).required(),
+})
+
+export type RemoteBannerType = InferType
+
+export enum RemoteBannerRedirectionType {
+ STORE = 'store',
+ EXTERNAL = 'external',
+}
+
+export const validateRemoteBanner = (objectToValidate: unknown): RemoteBannerType | null => {
+ try {
+ return remoteBannerSchema.validateSync(objectToValidate)
+ } catch (error) {
+ eventMonitoring.captureException(new Error(`RemoteBanner validation issue: ${String(error)}`), {
+ extra: { objectToValidate },
+ })
+ return null // Should handle case when null in calling component
+ }
+}
diff --git a/src/libs/analytics/__mocks__/logEventAnalytics.ts b/src/libs/analytics/__mocks__/logEventAnalytics.ts
index 379c23cdda0..091987a402f 100644
--- a/src/libs/analytics/__mocks__/logEventAnalytics.ts
+++ b/src/libs/analytics/__mocks__/logEventAnalytics.ts
@@ -94,8 +94,9 @@ export const logEventAnalytics: typeof actualLogEventAnalytics = {
logHasChosenPrice: jest.fn(),
logHasChosenTime: jest.fn(),
logHasClickedDuoStep: jest.fn(),
- logHasClickedTutorialFAQ: jest.fn(),
logHasClickedMissingCode: jest.fn(),
+ logHasClickedRemoteBanner: jest.fn(),
+ logHasClickedTutorialFAQ: jest.fn(),
logHasCorrectedEmail: jest.fn(),
logHasDismissedAppSharingModal: jest.fn(),
logHasDismissedModal: jest.fn(),
diff --git a/src/libs/analytics/logEventAnalytics.ts b/src/libs/analytics/logEventAnalytics.ts
index 234b33a94df..870e166ba79 100644
--- a/src/libs/analytics/logEventAnalytics.ts
+++ b/src/libs/analytics/logEventAnalytics.ts
@@ -24,6 +24,8 @@ import {
} from 'features/navigation/RootNavigator/types'
import { SearchStackRouteName } from 'features/navigation/SearchStackNavigator/types'
import { PlaylistType } from 'features/offer/enums'
+import { RemoteBannerOrigin } from 'features/remoteBanner/components/RemoteBanner'
+import { RemoteBannerType } from 'features/remoteBanner/components/remoteBannerSchema'
import { SearchState } from 'features/search/types'
import { ShareAppModalType } from 'features/share/types'
import { SubscriptionAnalyticsParams } from 'features/subscription/types'
@@ -356,6 +358,8 @@ export const logEventAnalytics = {
logHasClickedDuoStep: () => analytics.logEvent({ firebase: AnalyticsEvent.HAS_CLICKED_DUO_STEP }),
logHasClickedMissingCode: () =>
analytics.logEvent({ firebase: AnalyticsEvent.HAS_CLICKED_MISSING_CODE }),
+ logHasClickedRemoteBanner: (from: RemoteBannerOrigin, options: RemoteBannerType) =>
+ analytics.logEvent({ firebase: AnalyticsEvent.HAS_CLICKED_REMOTE_BANNER }, { from, options }),
logHasClickedTutorialFAQ: () =>
analytics.logEvent({ firebase: AnalyticsEvent.HAS_CLICKED_TUTORIAL_FAQ }),
logHasCorrectedEmail: ({ from }: { from: Referrals }) =>
diff --git a/src/libs/firebase/analytics/events.ts b/src/libs/firebase/analytics/events.ts
index ee6cd806750..f018374f37d 100644
--- a/src/libs/firebase/analytics/events.ts
+++ b/src/libs/firebase/analytics/events.ts
@@ -90,6 +90,7 @@ export enum AnalyticsEvent {
HAS_CHOSEN_TIME = 'HasChosenTime',
HAS_CLICKED_DUO_STEP = 'HasClickedDuoStep',
HAS_CLICKED_MISSING_CODE = 'HasClickedMissingCode',
+ HAS_CLICKED_REMOTE_BANNER = 'HasClickedRemoteBanner',
HAS_CLICKED_TUTORIAL_FAQ = 'HasClickedTutorialFAQ',
HAS_CORRECTED_EMAIL = 'HasCorrectedEmail',
HAS_DISMISSED_APP_SHARING_MODAL = 'HasDismissedAppSharingModal',
diff --git a/src/libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags.tsx b/src/libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags.tsx
index 1f354886e20..0abf725a480 100644
--- a/src/libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags.tsx
+++ b/src/libs/firebase/firestore/featureFlags/__tests__/setFeatureFlags.tsx
@@ -1,7 +1,22 @@
-import * as useFeatureFlagAPI from 'libs/firebase/firestore/featureFlags/useFeatureFlag'
+import * as useFeatureFlagOptionsAPI from 'libs/firebase/firestore/featureFlags/useFeatureFlagOptions'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
-export const setFeatureFlags = (activeFeatureFlags: RemoteStoreFeatureFlags[] = []) => {
- const useFeatureFlagSpy = jest.spyOn(useFeatureFlagAPI, 'useFeatureFlag')
- useFeatureFlagSpy.mockImplementation((flag) => activeFeatureFlags.includes(flag))
+type ActiveFeatureFlag = {
+ featureFlag: RemoteStoreFeatureFlags
+ options: Record
+}
+
+type ActiveFeatureFlags = (RemoteStoreFeatureFlags | ActiveFeatureFlag)[]
+
+export const setFeatureFlags = (activeFeatureFlags: ActiveFeatureFlags = []) => {
+ const useFeatureFlagSpy = jest.spyOn(useFeatureFlagOptionsAPI, 'useFeatureFlagOptions')
+ useFeatureFlagSpy.mockImplementation((flag): useFeatureFlagOptionsAPI.FeatureFlagOptions => {
+ const featureFlagRecord = activeFeatureFlags.find(
+ (item) => typeof item === 'object' && flag === item.featureFlag
+ )
+ return {
+ isFeatureFlagActive: activeFeatureFlags.includes(flag) || !!featureFlagRecord,
+ options: typeof featureFlagRecord === 'object' ? featureFlagRecord.options : undefined,
+ }
+ })
}
diff --git a/src/libs/firebase/firestore/featureFlags/types.ts b/src/libs/firebase/firestore/featureFlags/types.ts
index c189fcdc77f..40d4afb451a 100644
--- a/src/libs/firebase/firestore/featureFlags/types.ts
+++ b/src/libs/firebase/firestore/featureFlags/types.ts
@@ -1,7 +1,15 @@
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
import { FirebaseFirestoreTypes } from 'libs/firebase/shims/firestore'
-export type FeatureFlagConfig = { minimalBuildNumber?: number; maximalBuildNumber?: number }
+export type squads = 'decouverte' | 'activation' | 'conversion'
+
+export type FeatureFlagConfig = {
+ minimalBuildNumber?: number
+ maximalBuildNumber?: number
+ owner?: squads
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ options?: Record // Tried with unknown but got: Type 'Record' is not assignable to type 'DocumentFieldType'.
+}
export type FeatureFlagStore = Record
diff --git a/src/libs/firebase/firestore/featureFlags/useFeatureFlag.ts b/src/libs/firebase/firestore/featureFlags/useFeatureFlag.ts
index c017dcab0cd..64328a23543 100644
--- a/src/libs/firebase/firestore/featureFlags/useFeatureFlag.ts
+++ b/src/libs/firebase/firestore/featureFlags/useFeatureFlag.ts
@@ -1,51 +1,6 @@
-import { onlineManager, useQuery } from 'react-query'
-
-import { getAllFeatureFlags } from 'libs/firebase/firestore/featureFlags/getAllFeatureFlags'
-import { FeatureFlagConfig } from 'libs/firebase/firestore/featureFlags/types'
+import { useFeatureFlagOptions } from 'libs/firebase/firestore/featureFlags/useFeatureFlagOptions'
import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
-import { useLogTypeFromRemoteConfig } from 'libs/hooks/useLogTypeFromRemoteConfig'
-import { LogTypeEnum } from 'libs/monitoring/errors'
-import { eventMonitoring } from 'libs/monitoring/services'
-import { getAppBuildVersion } from 'libs/packageJson'
-import { QueryKeys } from 'libs/queryKeys'
-
-const appBuildVersion = getAppBuildVersion()
-// firestore feature flag documentation:
-// https://www.notion.so/passcultureapp/Feature-Flag-e7b0da7946f64020b8403e3581b4ed42#fff5fb17737240c9996c432117acacd8
export const useFeatureFlag = (featureFlag: RemoteStoreFeatureFlags): boolean => {
- const { data: docSnapshot } = useQuery(QueryKeys.FEATURE_FLAGS, getAllFeatureFlags, {
- staleTime: 1000 * 30, // 30 seconds
- enabled: onlineManager.isOnline(),
- })
- const { logType } = useLogTypeFromRemoteConfig()
-
- if (!docSnapshot) return false
-
- const { minimalBuildNumber, maximalBuildNumber } =
- docSnapshot.get(featureFlag) ?? {}
-
- if (minimalBuildNumber === undefined && maximalBuildNumber === undefined) return false
-
- if (
- !!(minimalBuildNumber && maximalBuildNumber) &&
- minimalBuildNumber > maximalBuildNumber &&
- logType === LogTypeEnum.INFO
- ) {
- eventMonitoring.captureException(
- `Minimal build number is greater than maximal build number for feature flag ${featureFlag}`,
- {
- level: logType,
- extra: {
- minimalBuildNumber,
- maximalBuildNumber,
- },
- }
- )
- return false
- }
- return (
- (!minimalBuildNumber || minimalBuildNumber <= appBuildVersion) &&
- (!maximalBuildNumber || maximalBuildNumber >= appBuildVersion)
- )
+ return useFeatureFlagOptions(featureFlag).isFeatureFlagActive
}
diff --git a/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.native.test.ts b/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.native.test.ts
new file mode 100644
index 00000000000..4b2ddcab021
--- /dev/null
+++ b/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.native.test.ts
@@ -0,0 +1,260 @@
+import { useFeatureFlagOptions } from 'libs/firebase/firestore/featureFlags/useFeatureFlagOptions'
+import {
+ FIRESTORE_ROOT_COLLECTION,
+ RemoteStoreDocuments,
+ RemoteStoreFeatureFlags,
+} from 'libs/firebase/firestore/types'
+import { DEFAULT_REMOTE_CONFIG } from 'libs/firebase/remoteConfig/remoteConfig.constants'
+import * as useRemoteConfigContext from 'libs/firebase/remoteConfig/RemoteConfigProvider'
+import firestore from 'libs/firebase/shims/firestore'
+import { eventMonitoring } from 'libs/monitoring/services'
+import { getAppBuildVersion } from 'libs/packageJson'
+import { reactQueryProviderHOC } from 'tests/reactQueryProviderHOC'
+import { act, renderHook } from 'tests/utils'
+
+const buildVersion = getAppBuildVersion()
+
+jest.mock('libs/monitoring/services')
+jest.mock('@react-native-firebase/firestore')
+
+const { collection } = firestore()
+
+const mockGet = jest.fn()
+
+const featureFlag = RemoteStoreFeatureFlags.WIP_DISABLE_STORE_REVIEW
+const useRemoteConfigContextSpy = jest.spyOn(useRemoteConfigContext, 'useRemoteConfigContext')
+
+describe('useFeatureFlagOptions', () => {
+ beforeAll(() =>
+ collection(FIRESTORE_ROOT_COLLECTION)
+ .doc(RemoteStoreDocuments.FEATURE_FLAGS)
+ // @ts-expect-error is a mock
+ .get.mockResolvedValue({
+ get: mockGet,
+ })
+ )
+
+ it('should deactivate FF when no build number is given', async () => {
+ const firestoreData = {}
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeFalsy()
+ })
+
+ describe('minimalBuildNumber', () => {
+ it.each`
+ firebaseFeatureFlag | minimalBuildNumber | expected
+ ${false} | ${buildVersion + 1} | ${'disabled when build number is below firestore minimalBuildNumber'}
+ ${true} | ${buildVersion} | ${'enabled when build number is equal to firestore minimalBuildNumber'}
+ ${true} | ${buildVersion - 1} | ${'enabled when build number is greater than firestore minimalBuildNumber'}
+ `(
+ `should be $expected`,
+ async ({
+ firebaseFeatureFlag,
+ minimalBuildNumber,
+ }: {
+ firebaseFeatureFlag: boolean
+ minimalBuildNumber: number
+ }) => {
+ const firestoreData = { minimalBuildNumber }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBe(firebaseFeatureFlag)
+ }
+ )
+ })
+
+ describe('maximalBuildNumber', () => {
+ it('should activate FF when version is below maximalBuildNumber', async () => {
+ const firestoreData = { maximalBuildNumber: buildVersion + 1 }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeTruthy()
+ })
+
+ it('should activate FF when version is equal to maximalBuildNumber', async () => {
+ const firestoreData = { maximalBuildNumber: buildVersion }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current).toBeTruthy()
+ })
+
+ it('should deactivate FF when version is greater than maximalBuildNumber', async () => {
+ const firestoreData = { maximalBuildNumber: buildVersion - 1 }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeFalsy()
+ })
+ })
+
+ describe('maximal and minimal build numbers', () => {
+ it('should activate FF when version is between minimalBuildNumber and maximalBuildNumber', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion - 1,
+ maximalBuildNumber: buildVersion + 1,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeTruthy()
+ })
+
+ it('should activate FF when minimalBuildNumber and maximalBuildNumber are equal to current version', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion,
+ maximalBuildNumber: buildVersion,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current).toBeTruthy()
+ })
+
+ it('should deactivate FF when minimalBuildNumber and maximalBuildNumber are equal and below current version', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion - 1,
+ maximalBuildNumber: buildVersion - 1,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeFalsy()
+ })
+
+ it('should deactivate FF when minimalBuildNumber and maximalBuildNumber are equal and greater than current version', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion + 1,
+ maximalBuildNumber: buildVersion + 1,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeFalsy()
+ })
+
+ it('should deactivate FF when minimalBuildNumber is greater than maximalBuildNumber', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion + 1,
+ maximalBuildNumber: buildVersion,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.isFeatureFlagActive).toBeFalsy()
+ })
+
+ describe('When shouldLogInfo remote config is false', () => {
+ beforeAll(() => {
+ useRemoteConfigContextSpy.mockReturnValue({
+ ...DEFAULT_REMOTE_CONFIG,
+ shouldLogInfo: false,
+ })
+ })
+
+ it('should not log to sentry when minimalBuildNumber is greater than maximalBuildNumber', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion + 1,
+ maximalBuildNumber: buildVersion,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(eventMonitoring.captureException).toHaveBeenCalledTimes(0)
+ })
+ })
+
+ describe('When shouldLogInfo remote config is true', () => {
+ beforeAll(() => {
+ useRemoteConfigContextSpy.mockReturnValue({
+ ...DEFAULT_REMOTE_CONFIG,
+ shouldLogInfo: true,
+ })
+ })
+
+ afterAll(() => {
+ useRemoteConfigContextSpy.mockReturnValue(DEFAULT_REMOTE_CONFIG)
+ })
+
+ it('should log to sentry when minimalBuildNumber is greater than maximalBuildNumber', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion + 1,
+ maximalBuildNumber: buildVersion,
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(eventMonitoring.captureException).toHaveBeenCalledWith(
+ `Minimal build number is greater than maximal build number for feature flag ${featureFlag}`,
+ { level: 'info', extra: firestoreData }
+ )
+ })
+ })
+ })
+
+ describe('other data', () => {
+ it('should include owner squad and options when included', async () => {
+ const firestoreData = {
+ minimalBuildNumber: buildVersion + 1,
+ maximalBuildNumber: buildVersion,
+ owner: 'activation',
+ options: {
+ option1: 'value1',
+ },
+ }
+ mockGet.mockReturnValueOnce(firestoreData)
+
+ const { result } = renderUseFeatureFlag(featureFlag)
+
+ await act(async () => {})
+
+ expect(result.current.options).toEqual({ option1: 'value1' })
+ expect(result.current.owner).toEqual('activation')
+ })
+ })
+})
+
+const renderUseFeatureFlag = (featureFlag: RemoteStoreFeatureFlags) =>
+ renderHook(() => useFeatureFlagOptions(featureFlag), {
+ wrapper: ({ children }) => reactQueryProviderHOC(children),
+ })
diff --git a/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.ts b/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.ts
new file mode 100644
index 00000000000..288fee70481
--- /dev/null
+++ b/src/libs/firebase/firestore/featureFlags/useFeatureFlagOptions.ts
@@ -0,0 +1,62 @@
+import { onlineManager, useQuery } from 'react-query'
+
+import { getAllFeatureFlags } from 'libs/firebase/firestore/featureFlags/getAllFeatureFlags'
+import { FeatureFlagConfig, squads } from 'libs/firebase/firestore/featureFlags/types'
+import { RemoteStoreFeatureFlags } from 'libs/firebase/firestore/types'
+import { useLogTypeFromRemoteConfig } from 'libs/hooks/useLogTypeFromRemoteConfig'
+import { LogTypeEnum } from 'libs/monitoring/errors'
+import { eventMonitoring } from 'libs/monitoring/services'
+import { getAppBuildVersion } from 'libs/packageJson'
+import { QueryKeys } from 'libs/queryKeys'
+
+const appBuildVersion = getAppBuildVersion()
+
+export type FeatureFlagOptions = {
+ isFeatureFlagActive: boolean
+ owner?: squads
+ options?: Record
+}
+
+// firestore feature flag documentation:
+// https://www.notion.so/passcultureapp/Feature-Flag-e7b0da7946f64020b8403e3581b4ed42#fff5fb17737240c9996c432117acacd8
+export const useFeatureFlagOptions = (featureFlag: RemoteStoreFeatureFlags): FeatureFlagOptions => {
+ const { data: docSnapshot, isLoading } = useQuery(QueryKeys.FEATURE_FLAGS, getAllFeatureFlags, {
+ staleTime: 1000 * 30, // 30 seconds
+ enabled: onlineManager.isOnline(),
+ })
+ const { logType } = useLogTypeFromRemoteConfig()
+
+ if (isLoading || !docSnapshot) return { isFeatureFlagActive: false }
+
+ const { minimalBuildNumber, maximalBuildNumber, options, owner } =
+ docSnapshot.get(featureFlag) ?? {}
+
+ if (minimalBuildNumber === undefined && maximalBuildNumber === undefined)
+ return { isFeatureFlagActive: false, owner, options }
+
+ if (
+ !!(minimalBuildNumber && maximalBuildNumber) &&
+ minimalBuildNumber > maximalBuildNumber &&
+ logType === LogTypeEnum.INFO
+ ) {
+ eventMonitoring.captureException(
+ `Minimal build number is greater than maximal build number for feature flag ${featureFlag}`,
+ {
+ level: logType,
+ extra: {
+ minimalBuildNumber,
+ maximalBuildNumber,
+ },
+ }
+ )
+ return { isFeatureFlagActive: false, owner, options }
+ }
+
+ return {
+ isFeatureFlagActive:
+ (!minimalBuildNumber || minimalBuildNumber <= appBuildVersion) &&
+ (!maximalBuildNumber || maximalBuildNumber >= appBuildVersion),
+ owner,
+ options,
+ }
+}
diff --git a/src/libs/firebase/firestore/types.ts b/src/libs/firebase/firestore/types.ts
index 03a45aeeea3..7a6de406956 100644
--- a/src/libs/firebase/firestore/types.ts
+++ b/src/libs/firebase/firestore/types.ts
@@ -54,7 +54,7 @@ export enum RemoteStoreFeatureFlags {
ENABLE_PACIFIC_FRANC_CURRENCY = 'enablePacificFrancCurrency',
ENABLE_PASS_FOR_ALL = 'enablePassForAll',
ENABLE_REPLICA_ALGOLIA_INDEX = 'enableReplicaAlgoliaIndex',
- SHOW_FORCE_UPDATE_BANNER = 'showForceUpdateBanner',
+ SHOW_REMOTE_BANNER = 'showRemoteBanner',
TARGET_XP_CINE_FROM_OFFER = 'targetXpCineFromOffer',
WIP_APP_V2_BUSINESS_BLOCK = 'wipAppV2BusinessBlock',
WIP_APP_V2_CATEGORY_BLOCK = 'wipAppV2CategoryBlock',
diff --git a/src/ui/components/ModuleBanner/BannerWithBackground.tsx b/src/ui/components/ModuleBanner/BannerWithBackground.tsx
index aba3424e2aa..c28c42b9099 100644
--- a/src/ui/components/ModuleBanner/BannerWithBackground.tsx
+++ b/src/ui/components/ModuleBanner/BannerWithBackground.tsx
@@ -25,6 +25,7 @@ type BannerWithBackgroundProps = TouchableProps & {
rightIcon?: FunctionComponent
backgroundSource?: ImageSourcePropType
testID?: string
+ disabled?: boolean
children: React.ReactNode
}
@@ -34,6 +35,7 @@ export const BannerWithBackground: FunctionComponent
children,
backgroundSource,
testID,
+ disabled = false,
...touchableProps
}) => {
const StyledLeftIcon =
@@ -54,7 +56,7 @@ export const BannerWithBackground: FunctionComponent
const TouchableComponent = 'navigateTo' in touchableProps ? StyledTouchableLink : TouchableOpacity
return (
-
+