Skip to content

Commit

Permalink
feat(points): points entry point and intro screen (#5406)
Browse files Browse the repository at this point in the history
### Description

* Add points card to the "Discover" screen
* Add an intro screen to the points home page (shown only once)


https://github.com/valora-inc/wallet/assets/2737872/ef769852-a628-4e4c-8ce1-63ffcbb0345b

### Test plan

Updated unit tests

### Related issues

- Fixes RET-1077

### Backwards compatibility

NA

### Network scalability

If a new NetworkId and/or Network are added in the future, the changes
in this PR will:

- [x] Continue to work without code changes, OR trigger a compilation
error (guaranteeing we find it when a new network is added)
  • Loading branch information
bakoushin authored May 16, 2024
1 parent 848148e commit 7eeac17
Show file tree
Hide file tree
Showing 26 changed files with 408 additions and 42 deletions.
Binary file added branding/celo/src/images/points-illustration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2338,6 +2338,15 @@
"moreComing": {
"title": "More coming soon!"
}
},
"discoverCard": {
"title": "{{appName}} Points",
"description": "Earn points easily with {{appName}}. Using the app, including swapping, leads to points!"
},
"intro": {
"title": "Earn points effortlessly",
"description": "Jump into a new way to earn! Your {{appName}} actions now earn points. Start earning today and unlock awesome benefits.",
"cta": "Start your points journey"
}
},
"earnFlow": {
Expand Down
2 changes: 1 addition & 1 deletion scripts/sync_branding.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ echo $mobile_root
cd "$mobile_root"

# Please update the sha when valora branding updates are needed
valora_branding_sha=f62cc4419260fbab46b886f5b515e6eb2281402a
valora_branding_sha=d55cad3a094165d124ada4d8c42a6401c5ec7f46

if [[ "$branding" == "valora" ]]; then
# prevents git from asking credentials
Expand Down
4 changes: 3 additions & 1 deletion src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -665,7 +665,9 @@ export enum JumpstartEvents {
}

export enum PointsEvents {
points_screen_open = 'points_screen_open',
points_discover_press = 'points_discover_press',
points_intro_dismiss = 'points_intro_dismiss',
points_intro_back = 'points_intro_back',
points_screen_back = 'points_screen_back',
points_screen_card_press = 'points_screen_card_press',
points_screen_card_cta_press = 'points_screen_card_cta_press',
Expand Down
4 changes: 3 additions & 1 deletion src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1558,7 +1558,9 @@ interface JumpstartEventsProperties {
}

interface PointsEventsProperties {
[PointsEvents.points_screen_open]: undefined
[PointsEvents.points_discover_press]: undefined
[PointsEvents.points_intro_dismiss]: undefined
[PointsEvents.points_intro_back]: undefined
[PointsEvents.points_screen_back]: undefined
[PointsEvents.points_screen_card_press]: {
activityId: PointsActivityId
Expand Down
4 changes: 3 additions & 1 deletion src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,9 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[RewardsEvents.claimed_reward]: ``,

// Events related to Valora Points program
[PointsEvents.points_screen_open]: `when Points home screen is opened`,
[PointsEvents.points_discover_press]: `when points card is pressed in the discover tab`,
[PointsEvents.points_intro_dismiss]: `when points intro is dismissed`,
[PointsEvents.points_intro_back]: `when back button is pressed from points intro screen`,
[PointsEvents.points_screen_back]: `when back button is pressed from Points home screen`,
[PointsEvents.points_screen_card_press]: `when an activity card is pressed from Points home screen`,
[PointsEvents.points_screen_card_cta_press]: `when a CTA is pressed on an activity card bottom sheet from the Points home screen`,
Expand Down
30 changes: 0 additions & 30 deletions src/components/PointsButton.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions src/dappsExplorer/TabDiscover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { currentLanguageSelector } from 'src/i18n/selectors'
import { Screens } from 'src/navigator/Screens'
import useScrollAwareHeader from 'src/navigator/ScrollAwareHeader'
import { StackParamList } from 'src/navigator/types'
import PointsDiscoverCard from 'src/points/PointsDiscoverCard'
import { useDispatch, useSelector } from 'src/redux/hooks'
import { Colors } from 'src/styles/colors'
import fontStyles, { typeScale } from 'src/styles/fonts'
Expand Down Expand Up @@ -235,6 +236,7 @@ function TabDiscover({ navigation }: Props) {
</Text>
}
<DappFeaturedActions onPressShowDappRankings={handleShowDappRankings} />
<PointsDiscoverCard />
<EarnCardDiscover
poolTokenId={networkConfig.aaveArbUsdcTokenId}
depositTokenId={networkConfig.arbUsdcTokenId}
Expand Down
1 change: 1 addition & 0 deletions src/images/Images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ export const earn1 = require('src/images/earn1.png')
export const earn2 = require('src/images/earn2.png')
export const earn3 = require('src/images/earn3.png')
export const lightningPhone = require('src/images/lightning-phone.png')
export const pointsIllustration = require('src/images/points-illustration.png')
5 changes: 0 additions & 5 deletions src/navigator/Headers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import CancelButton from 'src/components/CancelButton'
import CloseButton from 'src/components/CloseButton'
import CurrencyDisplay from 'src/components/CurrencyDisplay'
import LegacyTokenDisplay from 'src/components/LegacyTokenDisplay'
import PointsButton from 'src/components/PointsButton'
import QrScanButton from 'src/components/QrScanButton'
import TokenDisplay from 'src/components/TokenDisplay'
import NotificationBell from 'src/home/NotificationBell'
Expand All @@ -17,8 +16,6 @@ import BackChevronCentered from 'src/icons/BackChevronCentered'
import { navigateBack } from 'src/navigator/NavigationService'
import { TopBarIconButton } from 'src/navigator/TopBarButton'
import DisconnectBanner from 'src/shared/DisconnectBanner'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import colors from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
Expand Down Expand Up @@ -286,10 +283,8 @@ export function HeaderTitleWithSubtitle({
export const tabHeader: NativeStackNavigationOptions = {
...emptyHeader,
headerRight: () => {
const showPoints = getFeatureGate(StatsigFeatureGates.SHOW_POINTS)
return (
<View style={[styles.topElementsContainer, { marginRight: Spacing.Tiny4 }]}>
{showPoints && <PointsButton testID={'WalletHome/PointsButton'} />}
<QrScanButton testID="WalletHome/QRScanButton" />
<NotificationBell testID="WalletHome/NotificationBell" />
</View>
Expand Down
2 changes: 2 additions & 0 deletions src/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ import Welcome from 'src/onboarding/welcome/Welcome'
import PincodeEnter from 'src/pincode/PincodeEnter'
import PincodeSet from 'src/pincode/PincodeSet'
import PointsHome from 'src/points/PointsHome'
import PointsIntro from 'src/points/PointsIntro'
import { RootState } from 'src/redux/reducers'
import { store } from 'src/redux/store'
import SendConfirmation, { sendConfirmationScreenNavOptions } from 'src/send/SendConfirmation'
Expand Down Expand Up @@ -578,6 +579,7 @@ const assetScreens = (Navigator: typeof Stack) => (
const pointsScreens = (Navigator: typeof Stack) => (
<>
<Navigator.Screen name={Screens.PointsHome} component={PointsHome} options={noHeader} />
<Navigator.Screen name={Screens.PointsIntro} component={PointsIntro} options={noHeader} />
</>
)
const mapStateToProps = (state: RootState) => {
Expand Down
1 change: 1 addition & 0 deletions src/navigator/Screens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export enum Screens {
PincodeEnter = 'PincodeEnter',
PincodeSet = 'PincodeSet',
PointsHome = 'PointsHome',
PointsIntro = 'PointsIntro',
Profile = 'Profile',
ProfileMenu = 'ProfileMenu',
ProtectWallet = 'ProtectWallet',
Expand Down
1 change: 1 addition & 0 deletions src/navigator/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ export type StackParamList = {
}
| undefined
[Screens.PointsHome]: undefined
[Screens.PointsIntro]: undefined
[Screens.ProtectWallet]: undefined
[Screens.OnboardingRecoveryPhrase]: undefined
[Screens.Profile]: undefined
Expand Down
75 changes: 75 additions & 0 deletions src/points/PointsDiscoverCard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { fireEvent, render } from '@testing-library/react-native'
import React from 'react'
import { Provider } from 'react-redux'
import { PointsEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import PointsDiscoverCard from 'src/points/PointsDiscoverCard'
import { RootState } from 'src/redux/store'
import { getFeatureGate } from 'src/statsig/index'
import { RecursivePartial, createMockStore } from 'test/utils'

jest.mock('src/analytics/ValoraAnalytics')
jest.mock('src/statsig')

const renderPointsDiscoverCard = (storeOverrides?: RecursivePartial<RootState>) => {
const store = createMockStore(storeOverrides)

const tree = render(
<Provider store={store}>
<PointsDiscoverCard />
</Provider>
)

return {
store,
...tree,
}
}

describe('PointsDiscoverCard', () => {
beforeEach(() => {
jest.clearAllMocks()

jest.mocked(getFeatureGate).mockReturnValue(true)
})

it('renders when feature gate is enabled', () => {
const { getByText } = renderPointsDiscoverCard({ points: { pointsBalance: 'BALANCE_AMOUNT' } })

expect(getByText('points.discoverCard.title')).toBeTruthy()
expect(getByText('points.discoverCard.description')).toBeTruthy()
expect(getByText('BALANCE_AMOUNT')).toBeTruthy()
})

it('does not render when feature gate is disabled', () => {
jest.mocked(getFeatureGate).mockReturnValue(false)

const { queryByText } = renderPointsDiscoverCard()

expect(queryByText('points.discoverCard.title')).toBeFalsy()
expect(queryByText('points.discoverCard.description')).toBeFalsy()
})

it('tracks analytics event when pressed', () => {
const { getByText } = renderPointsDiscoverCard()

fireEvent.press(getByText('points.discoverCard.title'))
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_discover_press)
})

it('takes to the points intro screen if it has not been dismissed', () => {
const { getByText } = renderPointsDiscoverCard()

fireEvent.press(getByText('points.discoverCard.title'))
expect(navigate).toHaveBeenCalledWith(Screens.PointsIntro)
})

it('takes to the points home screen if intro has been dismissed', () => {
const { getByText } = renderPointsDiscoverCard({ points: { introHasBeenDismissed: true } })

fireEvent.press(getByText('points.discoverCard.title'))
expect(navigate).toHaveBeenCalledWith(Screens.PointsHome)
})
})
98 changes: 98 additions & 0 deletions src/points/PointsDiscoverCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import { PointsEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import Touchable from 'src/components/Touchable'
import LogoHeart from 'src/icons/LogoHeart'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { pointsBalanceSelector, pointsIntroHasBeenDismissedSelector } from 'src/points/selectors'
import { useSelector } from 'src/redux/hooks'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import { Colors } from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'

export default function PointsDiscoverCard() {
const showPoints = getFeatureGate(StatsigFeatureGates.SHOW_POINTS)

const { t } = useTranslation()
const pointsBalance = useSelector(pointsBalanceSelector)
const pointsIntroHasBeenDismissed = useSelector(pointsIntroHasBeenDismissedSelector)

const handlePress = () => {
ValoraAnalytics.track(PointsEvents.points_discover_press)
if (pointsIntroHasBeenDismissed) {
navigate(Screens.PointsHome)
} else {
navigate(Screens.PointsIntro)
}
}

if (!showPoints) {
return null
}

return (
<View style={styles.container}>
<Touchable
style={styles.touchable}
borderRadius={Spacing.Smallest8}
onPress={handlePress}
testID="PointsDiscoverCard"
>
<>
<View style={styles.header}>
<Text style={styles.title}>{t('points.discoverCard.title')}</Text>
<View style={styles.pill}>
<Text style={styles.balance}>{pointsBalance}</Text>
<LogoHeart size={Spacing.Regular16} />
</View>
</View>
<Text style={styles.description}>{t('points.discoverCard.description')}</Text>
</>
</Touchable>
</View>
)
}

const styles = StyleSheet.create({
container: {
marginBottom: Spacing.Thick24,
},
touchable: {
padding: Spacing.Regular16,
gap: Spacing.Smallest8,
borderColor: Colors.gray2,
borderWidth: 1,
borderRadius: Spacing.Smallest8,
},
header: {
flexDirection: 'row',
alignItems: 'flex-start',
justifyContent: 'space-between',
},
pill: {
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: Spacing.Small12,
paddingVertical: Spacing.Tiny4,
gap: Spacing.Tiny4,
backgroundColor: Colors.successLight,
borderRadius: 100,
},
title: {
...typeScale.labelSemiBoldMedium,
color: Colors.black,
},
description: {
...typeScale.bodyXSmall,
color: Colors.gray4,
},
balance: {
...typeScale.labelSemiBoldXSmall,
color: Colors.successDark,
},
})
Loading

0 comments on commit 7eeac17

Please sign in to comment.