diff --git a/__snapshots__/features/share/pages/ShareAppModal.native.test.tsx.native-snap b/__snapshots__/features/share/pages/ShareAppModal.native.test.tsx.native-snap index bef6e4b734c..ee6fee64b45 100644 --- a/__snapshots__/features/share/pages/ShareAppModal.native.test.tsx.native-snap +++ b/__snapshots__/features/share/pages/ShareAppModal.native.test.tsx.native-snap @@ -5,7 +5,7 @@ exports[`ShareAppModal should match snapshot 1`] = ` accessibilityLabelledBy="testUuidV4" animationType="fade" hardwareAccelerated={false} - onRequestClose={[Function]} + onRequestClose={[MockFunction]} statusBarTranslucent={true} transparent={true} visible={true} @@ -229,15 +229,12 @@ exports[`ShareAppModal should match snapshot 1`] = ` ] } /> - - 35 % des jeunes en France n’ont pas encore le pass Culture. - - + + 35 % des jeunes en France n’ont pas encore le pass Culture. + + - Fais découvrir le pass à tes amis ! - - - + Fais découvrir le pass à tes amis ! + - - undefined-SVG-Mock - - - undefined-SVG-Mock - - Partager l’appli - + + + undefined-SVG-Mock + + + + Partager l’appli + + - - - - - - button-icon-left-SVG-Mock - + + + button-icon-left-SVG-Mock + + + - + > + Non merci + - - Non merci - diff --git a/__snapshots__/features/share/pages/ShareAppModalVersionA.native.test.tsx.native-snap b/__snapshots__/features/share/pages/ShareAppModalVersionA.native.test.tsx.native-snap new file mode 100644 index 00000000000..b08bc6683ed --- /dev/null +++ b/__snapshots__/features/share/pages/ShareAppModalVersionA.native.test.tsx.native-snap @@ -0,0 +1,599 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ShareAppModalVersionA should match snapshot 1`] = ` + + + + + + + + + La culture, ça ce partage ! + + + + + + + rightIcon-SVG-Mock + + + + + + + + + + + + + + undefined-SVG-Mock + + + + + + 35 % des jeunes en France n’ont pas encore le pass Culture. + + + + Fais découvrir le pass à tes amis ! + + + + + + + + button-icon-left-SVG-Mock + + + + + + Partager l’app + + + + + + + + + button-icon-left-SVG-Mock + + + + + + Non merci + + + + + + + + + + + +`; diff --git a/__snapshots__/features/share/pages/ShareAppModalVersionB.native.test.tsx.native-snap b/__snapshots__/features/share/pages/ShareAppModalVersionB.native.test.tsx.native-snap new file mode 100644 index 00000000000..3d4aea2f25e --- /dev/null +++ b/__snapshots__/features/share/pages/ShareAppModalVersionB.native.test.tsx.native-snap @@ -0,0 +1,599 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ShareAppModalVersionB should match snapshot 1`] = ` + + + + + + + + + La culture, ça ce partage ! + + + + + + + rightIcon-SVG-Mock + + + + + + + + + + + + + + undefined-SVG-Mock + + + + + + 35 % des jeunes en France n’ont pas encore le pass Culture. + + + + Fais découvrir le pass à tes amis ! + + + + + + + + button-icon-left-SVG-Mock + + + + + + Partager l’app + + + + + + + + + button-icon-left-SVG-Mock + + + + + + Non merci + + + + + + + + + + + +`; diff --git a/src/features/internal/cheatcodes/pages/AppComponents/illustrationsExports.ts b/src/features/internal/cheatcodes/pages/AppComponents/illustrationsExports.ts index d76c5aca28d..b462a365e52 100644 --- a/src/features/internal/cheatcodes/pages/AppComponents/illustrationsExports.ts +++ b/src/features/internal/cheatcodes/pages/AppComponents/illustrationsExports.ts @@ -16,6 +16,8 @@ import { BicolorIdCardWithMagnifyingGlass } from 'ui/svg/icons/BicolorIdCardWith import { BicolorPhonePending } from 'ui/svg/icons/BicolorPhonePending' import { BicolorProfileDeletion } from 'ui/svg/icons/BicolorProfileDeletion' import { BicolorSadFace } from 'ui/svg/icons/BicolorSadFace' +import { BicolorShareChat } from 'ui/svg/icons/BicolorShareChat' +import { BicolorSharePhones } from 'ui/svg/icons/BicolorSharePhones' import { BicolorTicketBooked } from 'ui/svg/icons/BicolorTicketBooked' import { BicolorUserFavorite } from 'ui/svg/icons/BicolorUserFavorite' import { CalendarIllustration } from 'ui/svg/icons/CalendarIllustration' @@ -52,6 +54,8 @@ export const BicolorIllustrations = { BicolorUserError, BicolorUserFavorite, BicolorUserIdentification, + BicolorSharePhones, + BicolorShareChat, } export const UniqueColorIllustrations = { diff --git a/src/features/share/api/useShareAppModalViewmodel.native.test.ts b/src/features/share/api/useShareAppModalViewmodel.native.test.ts new file mode 100644 index 00000000000..47e96cb4f2c --- /dev/null +++ b/src/features/share/api/useShareAppModalViewmodel.native.test.ts @@ -0,0 +1,140 @@ +import * as shareApp from 'features/share/helpers/shareApp' +import { ShareAppModalType } from 'features/share/types' +import { analytics } from 'libs/analytics/__mocks__/provider' +import { DEFAULT_REMOTE_CONFIG } from 'libs/firebase/remoteConfig/remoteConfig.constants' +import { CustomRemoteConfig } from 'libs/firebase/remoteConfig/remoteConfig.types' +import * as useRemoteConfigContext from 'libs/firebase/remoteConfig/RemoteConfigProvider' +import { renderHook } from 'tests/utils' + +import { + ShareAppModalSelectorViewmodelParams, + useShareAppModalViewmodel, +} from './useShareAppModalViewmodel' + +const useRemoteConfigContextSpy = jest.spyOn(useRemoteConfigContext, 'useRemoteConfigContext') +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter') + +const mockShareApp = jest.spyOn(shareApp, 'shareApp') +mockShareApp.mockImplementation(jest.fn()) + +const useShareAppModalSelectorViewmodelTest = ({ + hideModal = jest.fn(), + type: type = ShareAppModalType.BENEFICIARY, + showModal = jest.fn(), + setType = jest.fn(), +}: Partial = {}) => { + const hook = renderHook(() => + // eslint-disable-next-line react-hooks/rules-of-hooks + useShareAppModalViewmodel({ + type, + hideModal, + showModal, + setType, + }) + ) + + return hook.result.current +} + +const givenRemoteConfigShareAppModalVersion = ( + version: CustomRemoteConfig['shareAppModalVersion'] +) => { + useRemoteConfigContextSpy.mockReturnValue({ + ...DEFAULT_REMOTE_CONFIG, + shareAppModalVersion: version, + }) +} + +describe('useShareAppModalViewmodel', () => { + describe('version', () => { + test.each` + expectedVersion + ${'default'} + ${'version_1'} + `('get version $expectedVersion from remote config', async ({ expectedVersion }) => { + givenRemoteConfigShareAppModalVersion(expectedVersion) + const { version } = useShareAppModalSelectorViewmodelTest() + + expect(version).toEqual(expectedVersion) + }) + }) + + describe('close', () => { + test('close modal on close', async () => { + const hideModal = jest.fn() + + const { close } = useShareAppModalSelectorViewmodelTest({ hideModal }) + + close() + + expect(hideModal).toHaveBeenCalledWith() + }) + + test.each` + type + ${ShareAppModalType.BENEFICIARY} + ${ShareAppModalType.NOT_ELIGIBLE} + `('log dismiss share app on close with type $type', async ({ type }) => { + const { close } = useShareAppModalSelectorViewmodelTest({ type }) + + close() + + expect(analytics.logDismissShareApp).toHaveBeenCalledWith(type) + }) + }) + + describe('share', () => { + test('close modal on share', async () => { + const hideModal = jest.fn() + + const { share } = useShareAppModalSelectorViewmodelTest({ hideModal }) + + await share() + + expect(hideModal).toHaveBeenCalledWith() + }) + + test.each` + type + ${ShareAppModalType.BENEFICIARY} + ${ShareAppModalType.NOT_ELIGIBLE} + `('log share app on share with type $type', async ({ type }) => { + const { share } = useShareAppModalSelectorViewmodelTest({ type }) + + await share() + + expect(analytics.logShareApp).toHaveBeenCalledWith({ type }) + }) + }) + + describe('show', () => { + test.each` + type + ${ShareAppModalType.BENEFICIARY} + ${ShareAppModalType.NOT_ELIGIBLE} + `('show modal on show with type $type', async ({ type }) => { + const showModal = jest.fn() + const setType = jest.fn() + const { show } = useShareAppModalSelectorViewmodelTest({ showModal, setType }) + + show(type) + + expect(setType).toHaveBeenCalledWith(type) + expect(showModal).toHaveBeenCalledWith() + }) + + test.each` + type + ${ShareAppModalType.BENEFICIARY} + ${ShareAppModalType.NOT_ELIGIBLE} + `('send analytics on show with $type', ({ type }) => { + const { show } = useShareAppModalSelectorViewmodelTest() + + show(type) + + expect(analytics.logShowShareAppModal).toHaveBeenCalledWith({ + type, + }) + }) + }) +}) diff --git a/src/features/share/api/useShareAppModalViewmodel.ts b/src/features/share/api/useShareAppModalViewmodel.ts new file mode 100644 index 00000000000..8dc25606d4e --- /dev/null +++ b/src/features/share/api/useShareAppModalViewmodel.ts @@ -0,0 +1,49 @@ +import { useCallback } from 'react' + +import { shareApp } from 'features/share/helpers/shareApp' +import { ShareAppModalType } from 'features/share/types' +import { analytics } from 'libs/analytics' +import { useRemoteConfigContext } from 'libs/firebase/remoteConfig/RemoteConfigProvider' + +export type ShareAppModalSelectorViewmodelParams = { + type: ShareAppModalType + hideModal: () => void + setType: (type: ShareAppModalType) => void + showModal: () => void +} + +export const useShareAppModalViewmodel = ({ + hideModal, + type, + showModal, + setType, +}: ShareAppModalSelectorViewmodelParams) => { + const { shareAppModalVersion: version } = useRemoteConfigContext() + + const close = useCallback(() => { + analytics.logDismissShareApp(type) + hideModal() + }, [hideModal, type]) + + const share = useCallback(async () => { + analytics.logShareApp({ type: type }) + await shareApp(ShareAppModalType.BENEFICIARY ? 'beneficiary_modal' : 'uneligible_modal') + hideModal() + }, [hideModal, type]) + + const show = useCallback( + (_type: ShareAppModalType) => { + analytics.logShowShareAppModal({ type: _type }) + setType(_type) + showModal() + }, + [setType, showModal] + ) + + return { + version, + share, + close, + show, + } +} diff --git a/src/features/share/components/ShareAppModalSelector.tsx b/src/features/share/components/ShareAppModalSelector.tsx new file mode 100644 index 00000000000..e4cea0d7557 --- /dev/null +++ b/src/features/share/components/ShareAppModalSelector.tsx @@ -0,0 +1,25 @@ +import React, { FC } from 'react' + +import { ShareAppModal } from 'features/share/pages/ShareAppModal' +import { ShareAppModalVersionA } from 'features/share/pages/ShareAppModalVersionA' +import { ShareAppModalVersionB } from 'features/share/pages/ShareAppModalVersionB' +import { CustomRemoteConfig } from 'libs/firebase/remoteConfig/remoteConfig.types' + +type SelectorProps = { + visible: boolean + close: () => void + share: () => void + version: CustomRemoteConfig['shareAppModalVersion'] +} + +export const ShareAppModalSelector: FC = ({ visible, close, share, version }) => { + if (version === 'A') { + return + } + + if (version === 'B') { + return + } + + return +} diff --git a/src/features/share/context/ShareAppWrapper.native.test.tsx b/src/features/share/context/ShareAppWrapper.native.test.tsx deleted file mode 100644 index 1f177f6e87b..00000000000 --- a/src/features/share/context/ShareAppWrapper.native.test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { ShareAppWrapper, useShareAppContext } from 'features/share/context/ShareAppWrapper' -import { ShareAppModalType } from 'features/share/types' -import { analytics } from 'libs/analytics' -import { renderHook, act } from 'tests/utils' - -const mockShowModal = jest.fn() -const mockShareAppModal = jest.fn() - -jest.mock('ui/components/modals/useModal', () => ({ - useModal: () => ({ - visible: false, - showModal: mockShowModal, - hideModal: jest.fn(), - toggleModal: jest.fn(), - }), -})) - -jest.mock('features/share/pages/ShareAppModal', () => ({ - ShareAppModal: ({ modalType }: { modalType: ShareAppModalType }) => { - mockShareAppModal(modalType) - return null - }, -})) - -describe('useShareAppContext()', () => { - it.each([ - ShareAppModalType.BENEFICIARY, - ShareAppModalType.NOT_ELIGIBLE, - ShareAppModalType.ON_BOOKING_SUCCESS, - ])('should show modal with correct modal type', async (modalType) => { - const { result } = renderShareAppHook() - - await act(async () => { - result.current.showShareAppModal(modalType) - }) - - expect(mockShowModal).toHaveBeenCalledTimes(1) - expect(mockShareAppModal).toHaveBeenCalledWith(modalType) - }) - - it.each([ - ShareAppModalType.BENEFICIARY, - ShareAppModalType.NOT_ELIGIBLE, - ShareAppModalType.ON_BOOKING_SUCCESS, - ])('should log analytics when displaying modal', async (modalType) => { - const { result } = renderShareAppHook() - - await act(async () => { - result.current.showShareAppModal(modalType) - }) - - expect(analytics.logShowShareAppModal).toHaveBeenCalledWith({ type: modalType }) - }) -}) - -function renderShareAppHook() { - return renderHook(useShareAppContext, { - wrapper: ShareAppWrapper, - }) -} diff --git a/src/features/share/context/ShareAppWrapper.tsx b/src/features/share/context/ShareAppWrapper.tsx index 56e48fe7259..8148f2602ac 100644 --- a/src/features/share/context/ShareAppWrapper.tsx +++ b/src/features/share/context/ShareAppWrapper.tsx @@ -1,10 +1,11 @@ -import React, { memo, useCallback, useContext, useMemo, useState } from 'react' +import React, { memo, useContext, useMemo, useState } from 'react' -import { ShareAppModal } from 'features/share/pages/ShareAppModal' import { ShareAppModalType } from 'features/share/types' -import { analytics } from 'libs/analytics' import { useModal } from 'ui/components/modals/useModal' +import { useShareAppModalViewmodel } from '../api/useShareAppModalViewmodel' +import { ShareAppModalSelector } from '../components/ShareAppModalSelector' + interface ShareAppContextValue { showShareAppModal: (modalType: ShareAppModalType) => void } @@ -18,29 +19,27 @@ export const ShareAppWrapper = memo(function ShareAppWrapper({ }: { children: React.JSX.Element }) { - const { showModal, ...shareAppModalProps } = useModal(false) + const { showModal, visible, hideModal } = useModal(false) const [modalType, setModalType] = useState(ShareAppModalType.NOT_ELIGIBLE) - const showShareAppModal = useCallback( - (modalType: ShareAppModalType) => { - analytics.logShowShareAppModal({ type: modalType }) - setModalType(modalType) - showModal() - }, - [showModal] - ) + const { close, share, show, version } = useShareAppModalViewmodel({ + hideModal, + showModal, + setType: setModalType, + type: modalType, + }) const value = useMemo( () => ({ - showShareAppModal, + showShareAppModal: show, }), - [showShareAppModal] + [show] ) return ( {children} - + ) }) diff --git a/src/features/share/pages/ShareAppModal.native.test.tsx b/src/features/share/pages/ShareAppModal.native.test.tsx index 39c08d3d21d..d8d93eabdc1 100644 --- a/src/features/share/pages/ShareAppModal.native.test.tsx +++ b/src/features/share/pages/ShareAppModal.native.test.tsx @@ -1,16 +1,9 @@ import React from 'react' -import * as Share from 'features/share/helpers/shareApp' -import { ShareAppModalType } from 'features/share/types' -import { analytics } from 'libs/analytics' -import { fireEvent, render, screen, waitFor } from 'tests/utils' +import { render, screen } from 'tests/utils' import { ShareAppModal } from './ShareAppModal' -const visible = true -const hideModal = jest.fn() -const shareApp = jest.spyOn(Share, 'shareApp').mockResolvedValue() - jest.mock('react-native/Libraries/Animated/createAnimatedComponent', () => { return function createAnimatedComponent(Component: unknown) { return Component @@ -19,72 +12,8 @@ jest.mock('react-native/Libraries/Animated/createAnimatedComponent', () => { describe('ShareAppModal', () => { it('should match snapshot', () => { - render( - - ) + render() expect(screen).toMatchSnapshot() }) - - it('should open native share modal when clicking on "Partager l’appli" button', async () => { - render( - - ) - - const shareButton = screen.getByTestId('Partager l’appli') - fireEvent.press(shareButton) - - await waitFor(() => { - expect(shareApp).toHaveBeenCalledTimes(1) - }) - }) - - it('should close modal when clicking on "Partager l’appli" button', () => { - render( - - ) - - const shareButton = screen.getByText('Partager l’appli') - fireEvent.press(shareButton) - - expect(hideModal).toHaveBeenCalledTimes(1) - }) - - it.each([ - ShareAppModalType.NOT_ELIGIBLE, - ShareAppModalType.BENEFICIARY, - ShareAppModalType.ON_BOOKING_SUCCESS, - ])('should log analytics when clicking on "Partager l’appli" button', (modalType) => { - render() - - const shareButton = screen.getByTestId('Partager l’appli') - fireEvent.press(shareButton) - - expect(analytics.logShareApp).toHaveBeenNthCalledWith(1, { type: modalType }) - }) - - it.each([ - ShareAppModalType.NOT_ELIGIBLE, - ShareAppModalType.BENEFICIARY, - ShareAppModalType.ON_BOOKING_SUCCESS, - ])('should log analytics when clicking on "Fermer la modale"', (modalType) => { - render() - - const closeButton = screen.getByTestId('Fermer la modale') - fireEvent.press(closeButton) - - expect(analytics.logDismissShareApp).toHaveBeenNthCalledWith(1, modalType) - }) }) diff --git a/src/features/share/pages/ShareAppModal.tsx b/src/features/share/pages/ShareAppModal.tsx index b2d2a823a6c..7678867467a 100644 --- a/src/features/share/pages/ShareAppModal.tsx +++ b/src/features/share/pages/ShareAppModal.tsx @@ -1,71 +1,51 @@ -import React, { FunctionComponent, useCallback } from 'react' +import React, { FunctionComponent } from 'react' import styled from 'styled-components/native' import { SHARE_APP_IMAGE_SOURCE } from 'features/share/components/shareAppImage' -import { shareApp } from 'features/share/helpers/shareApp' -import { ShareAppModalType } from 'features/share/types' -import { analytics } from 'libs/analytics' import { ButtonQuaternaryBlack } from 'ui/components/buttons/ButtonQuaternaryBlack' import { ButtonWithLinearGradient } from 'ui/components/buttons/buttonWithLinearGradient/ButtonWithLinearGradient' import { MarketingModal } from 'ui/components/modals/MarketingModal' +import { ViewGap } from 'ui/components/ViewGap/ViewGap' import { Share } from 'ui/svg/icons/BicolorShare' import { Invalidate } from 'ui/svg/icons/Invalidate' -import { Spacer, Typo } from 'ui/theme' +import { Typo } from 'ui/theme' import { LINE_BREAK } from 'ui/theme/constants' type Props = { visible: boolean - hideModal: () => void - modalType: ShareAppModalType + close: () => void + share: () => void } -export const ShareAppModal: FunctionComponent = ({ visible, hideModal, modalType }) => { - const openShareAppModal = useCallback(() => { - analytics.logShareApp({ type: modalType }) - hideModal() - const utmMedium = - modalType === ShareAppModalType.BENEFICIARY ? 'beneficiary_modal' : 'uneligible_modal' - setTimeout(() => shareApp(utmMedium), 0) - }, [modalType, hideModal]) - - const closeModal = useCallback(() => { - analytics.logDismissShareApp(modalType) - hideModal() - }, [modalType, hideModal]) - +export const ShareAppModal: FunctionComponent = ({ visible, close, share }) => { return ( - - - 35 % des jeunes en France n’ont pas encore le pass Culture. - - {LINE_BREAK} - Fais découvrir le pass à tes amis ! - - - - - - - + onBackdropPress={close}> + + + + 35 % des jeunes en France n’ont pas encore le pass Culture. + + {LINE_BREAK} + Fais découvrir le pass à tes amis ! + + + + + + ) } -const ButtonContainer = styled.View({ width: '100%' }) const StyledBody = styled(Typo.Body)({ textAlign: 'center', }) diff --git a/src/features/share/pages/ShareAppModal.web.test.tsx b/src/features/share/pages/ShareAppModal.web.test.tsx index 5c8939237bd..dd4bfe4236b 100644 --- a/src/features/share/pages/ShareAppModal.web.test.tsx +++ b/src/features/share/pages/ShareAppModal.web.test.tsx @@ -1,19 +1,12 @@ import React from 'react' -import { ShareAppModalType } from 'features/share/types' import { render } from 'tests/utils/web' import { ShareAppModal } from './ShareAppModal' describe('ShareAppModal', () => { - it.each([ - ShareAppModalType.NOT_ELIGIBLE, - ShareAppModalType.BENEFICIARY, - ShareAppModalType.ON_BOOKING_SUCCESS, - ])('should render null in web', (modalType) => { - const { container } = render( - - ) + it('should render null in web', () => { + const { container } = render() expect(container).toBeEmptyDOMElement() }) diff --git a/src/features/share/pages/ShareAppModalVersionA.native.test.tsx b/src/features/share/pages/ShareAppModalVersionA.native.test.tsx new file mode 100644 index 00000000000..ac7da2bab0e --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionA.native.test.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +import { render, screen } from 'tests/utils' + +import { ShareAppModalVersionA } from './ShareAppModalVersionA' + +jest.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: jest.fn(() => ({ bottom: 10 })), +})) + +describe('ShareAppModalVersionA', () => { + it('should match snapshot', () => { + render() + + expect(screen).toMatchSnapshot() + }) +}) diff --git a/src/features/share/pages/ShareAppModalVersionA.tsx b/src/features/share/pages/ShareAppModalVersionA.tsx new file mode 100644 index 00000000000..20de04377c9 --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionA.tsx @@ -0,0 +1,58 @@ +import React, { FC } from 'react' +import styled from 'styled-components/native' + +import { ButtonPrimary } from 'ui/components/buttons/ButtonPrimary' +import { ButtonTertiaryBlack } from 'ui/components/buttons/ButtonTertiaryBlack' +import { AppModal } from 'ui/components/modals/AppModal' +import { ViewGap } from 'ui/components/ViewGap/ViewGap' +import { Share } from 'ui/svg/icons/BicolorShare' +import { BicolorShareChat } from 'ui/svg/icons/BicolorShareChat' +import { Close } from 'ui/svg/icons/Close' +import { Invalidate } from 'ui/svg/icons/Invalidate' +import { TypoDS } from 'ui/theme' +import { LINE_BREAK } from 'ui/theme/constants' + +type Props = { + visible: boolean + close: () => void + share: () => void +} + +export const ShareAppModalVersionA: FC = ({ visible, close, share }) => { + return ( + + + + + + + 35 % des jeunes en France n’ont pas encore le pass Culture. + {LINE_BREAK} + Fais découvrir le pass à tes amis ! + + + + + + + + ) +} + +const StyledBody = styled(TypoDS.Body)({ + textAlign: 'center', +}) + +const ImageContainer = styled.View({ + alignItems: 'center', +}) + +const Icon = styled(BicolorShareChat).attrs(({ theme }) => ({ + size: theme.illustrations.sizes.medium, +}))({}) diff --git a/src/features/share/pages/ShareAppModalVersionA.web.test.tsx b/src/features/share/pages/ShareAppModalVersionA.web.test.tsx new file mode 100644 index 00000000000..bd0de4ae74f --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionA.web.test.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +import { render } from 'tests/utils/web' + +import { ShareAppModalVersionA } from './ShareAppModalVersionA' + +describe('ShareAppModalVersionA', () => { + it('should render null in web', () => { + const { container } = render( + + ) + + expect(container).toBeEmptyDOMElement() + }) +}) diff --git a/src/features/share/pages/ShareAppModalVersionA.web.tsx b/src/features/share/pages/ShareAppModalVersionA.web.tsx new file mode 100644 index 00000000000..053a09fa886 --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionA.web.tsx @@ -0,0 +1 @@ +export const ShareAppModalVersionA = () => null diff --git a/src/features/share/pages/ShareAppModalVersionB.native.test.tsx b/src/features/share/pages/ShareAppModalVersionB.native.test.tsx new file mode 100644 index 00000000000..821e48c6af2 --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionB.native.test.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +import { render, screen } from 'tests/utils' + +import { ShareAppModalVersionB } from './ShareAppModalVersionB' + +jest.mock('react-native-safe-area-context', () => ({ + useSafeAreaInsets: jest.fn(() => ({ bottom: 10 })), +})) + +describe('ShareAppModalVersionB', () => { + it('should match snapshot', () => { + render() + + expect(screen).toMatchSnapshot() + }) +}) diff --git a/src/features/share/pages/ShareAppModalVersionB.tsx b/src/features/share/pages/ShareAppModalVersionB.tsx new file mode 100644 index 00000000000..8aa04b7a9aa --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionB.tsx @@ -0,0 +1,58 @@ +import React, { FC } from 'react' +import styled from 'styled-components/native' + +import { ButtonPrimary } from 'ui/components/buttons/ButtonPrimary' +import { ButtonTertiaryBlack } from 'ui/components/buttons/ButtonTertiaryBlack' +import { AppModal } from 'ui/components/modals/AppModal' +import { ViewGap } from 'ui/components/ViewGap/ViewGap' +import { Share } from 'ui/svg/icons/BicolorShare' +import { BicolorSharePhones } from 'ui/svg/icons/BicolorSharePhones' +import { Close } from 'ui/svg/icons/Close' +import { Invalidate } from 'ui/svg/icons/Invalidate' +import { TypoDS } from 'ui/theme' +import { LINE_BREAK } from 'ui/theme/constants' + +type Props = { + visible: boolean + close: () => void + share: () => void +} + +export const ShareAppModalVersionB: FC = ({ visible, close, share }) => { + return ( + + + + + + + 35 % des jeunes en France n’ont pas encore le pass Culture. + {LINE_BREAK} + Fais découvrir le pass à tes amis ! + + + + + + + + ) +} + +const StyledBody = styled(TypoDS.Body)({ + textAlign: 'center', +}) + +const ImageContainer = styled.View({ + alignItems: 'center', +}) + +const Icon = styled(BicolorSharePhones).attrs(({ theme }) => ({ + size: theme.illustrations.sizes.medium, +}))({}) diff --git a/src/features/share/pages/ShareAppModalVersionB.web.test.tsx b/src/features/share/pages/ShareAppModalVersionB.web.test.tsx new file mode 100644 index 00000000000..af828c5f364 --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionB.web.test.tsx @@ -0,0 +1,15 @@ +import React from 'react' + +import { render } from 'tests/utils/web' + +import { ShareAppModalVersionB } from './ShareAppModalVersionB' + +describe('ShareAppModalVersionB', () => { + it('should render null in web', () => { + const { container } = render( + + ) + + expect(container).toBeEmptyDOMElement() + }) +}) diff --git a/src/features/share/pages/ShareAppModalVersionB.web.tsx b/src/features/share/pages/ShareAppModalVersionB.web.tsx new file mode 100644 index 00000000000..74a07b51061 --- /dev/null +++ b/src/features/share/pages/ShareAppModalVersionB.web.tsx @@ -0,0 +1 @@ +export const ShareAppModalVersionB = () => null diff --git a/src/libs/firebase/remoteConfig/helpers/getRemoteConfigFromConfigValues.ts b/src/libs/firebase/remoteConfig/helpers/getRemoteConfigFromConfigValues.ts index 320cec41ba0..74cbbaf010c 100644 --- a/src/libs/firebase/remoteConfig/helpers/getRemoteConfigFromConfigValues.ts +++ b/src/libs/firebase/remoteConfig/helpers/getRemoteConfigFromConfigValues.ts @@ -37,4 +37,7 @@ export const getRemoteConfigFromConfigValues = ( subscriptionHomeEntryIds: JSON.parse( getConfigValue(parameters.subscriptionHomeEntryIds).asString() ), + shareAppModalVersion: getConfigValue( + parameters.shareAppModalVersion + ).asString() as CustomRemoteConfig['shareAppModalVersion'], }) diff --git a/src/libs/firebase/remoteConfig/remoteConfig.constants.ts b/src/libs/firebase/remoteConfig/remoteConfig.constants.ts index a620b0d76e1..4c2debcbbe2 100644 --- a/src/libs/firebase/remoteConfig/remoteConfig.constants.ts +++ b/src/libs/firebase/remoteConfig/remoteConfig.constants.ts @@ -33,4 +33,5 @@ export const DEFAULT_REMOTE_CONFIG: CustomRemoteConfig = { [SubscriptionTheme.VISITES]: '', [SubscriptionTheme.ACTIVITES]: '', }, + shareAppModalVersion: 'default', } diff --git a/src/libs/firebase/remoteConfig/remoteConfig.types.ts b/src/libs/firebase/remoteConfig/remoteConfig.types.ts index 3474d8751bf..2b60f98dadd 100644 --- a/src/libs/firebase/remoteConfig/remoteConfig.types.ts +++ b/src/libs/firebase/remoteConfig/remoteConfig.types.ts @@ -21,6 +21,7 @@ export type CustomRemoteConfig = { shouldLogInfo: boolean displayInAppFeedback: boolean subscriptionHomeEntryIds: Record + shareAppModalVersion: 'default' | 'A' | 'B' } /* The purpose of GenericRemoteConfig is only to resolve type conflicts. diff --git a/src/ui/svg/icons/BicolorShareChat.tsx b/src/ui/svg/icons/BicolorShareChat.tsx new file mode 100644 index 00000000000..4bf97ad907b --- /dev/null +++ b/src/ui/svg/icons/BicolorShareChat.tsx @@ -0,0 +1,95 @@ +import * as React from 'react' +import { Defs, LinearGradient, Path, Stop } from 'react-native-svg' +import styled from 'styled-components/native' + +import { AccessibleIcon } from 'ui/svg/icons/types' +import { svgIdentifier } from 'ui/svg/utils' + +import { AccessibleSvg } from '../AccessibleSvg' + +const BicolorShareChatSVG: React.FunctionComponent = ({ + size, + color, + color2, + accessibilityLabel, + testID, + style, +}) => { + const height = typeof size === 'string' ? size : ((size as number) * 122) / 170 + + const { id: gradientId, fill: gradientFill } = svgIdentifier() + const { id: gradientId2, fill: gradientFill2 } = svgIdentifier() + const { id: gradientId3, fill: gradientFill3 } = svgIdentifier() + + return ( + + + + + + + + + + + + + + + + + + + + ) +} + +export const BicolorShareChat = styled(BicolorShareChatSVG).attrs( + ({ color, color2, size, theme }) => ({ + color: color ?? theme.colors.primary, + color2: color2 ?? color ?? theme.colors.secondary, + size: size ?? theme.illustrations.sizes.medium, + }) +)`` diff --git a/src/ui/svg/icons/BicolorSharePhones.tsx b/src/ui/svg/icons/BicolorSharePhones.tsx new file mode 100644 index 00000000000..2bb5238619e --- /dev/null +++ b/src/ui/svg/icons/BicolorSharePhones.tsx @@ -0,0 +1,108 @@ +import * as React from 'react' +import { Defs, LinearGradient, Path, Stop } from 'react-native-svg' +import styled from 'styled-components/native' + +import { AccessibleIcon } from 'ui/svg/icons/types' +import { svgIdentifier } from 'ui/svg/utils' + +import { AccessibleSvg } from '../AccessibleSvg' + +const BicolorSharePhonesSVG: React.FunctionComponent = ({ + size, + color, + color2, + accessibilityLabel, + testID, + style, +}) => { + const height = typeof size === 'string' ? size : ((size as number) * 128) / 139 + + const { id: gradientId, fill: gradientFill } = svgIdentifier() + const { id: gradientId2, fill: gradientFill2 } = svgIdentifier() + const { id: gradientId3, fill: gradientFill3 } = svgIdentifier() + const { id: gradientId4, fill: gradientFill4 } = svgIdentifier() + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + ) +} + +export const BicolorSharePhones = styled(BicolorSharePhonesSVG).attrs( + ({ color, color2, size, theme }) => ({ + color: color ?? theme.colors.primary, + color2: color2 ?? color ?? theme.colors.secondary, + size: size ?? theme.illustrations.sizes.medium, + }) +)``