Skip to content

Commit

Permalink
feat(points): Implement Points Home screen (valora-inc#5128)
Browse files Browse the repository at this point in the history
### Description

For
[ACT-1022](https://linear.app/valora/issue/RET-1022/[wallet]-build-points-landing-screen-add-statsig-config).
Adds in the Points home screen and sets up Statsig to store points
config information.

See the Statsig Dynamic Config
[here](https://console.statsig.com/4plizaPmWwPL21ASV4QAO0/dynamic_configs/points_config).
Points visibility is gated by the feature gate
[here](https://console.statsig.com/4plizaPmWwPL21ASV4QAO0/gates/show_points).

Note that at the moment, this PR is complete _except_ for:
* The actual Points logo/icon - I'm waiting on a high-quality vector
image before adding it to the branding repo.
* Some copy on the Swap bottom sheet.

I added in the functionality to automatically create "sections" on the
home screen for different point values - we won't need this immediately,
but it was kinda fun to add to figured why not :)

### Test plan

Unit and manual tested. See video below.



https://github.com/valora-inc/wallet/assets/569401/c854b3d3-31c8-4d54-92f2-e65ed6bc2b76


### Related issues

- Fixes
[ACT-1022](https://linear.app/valora/issue/RET-1022/[wallet]-build-points-landing-screen-add-statsig-config).

### Backwards compatibility

Yes.

### 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
jophish authored and shottah committed May 15, 2024
1 parent c54df7e commit 8278f6c
Show file tree
Hide file tree
Showing 23 changed files with 767 additions and 7 deletions.
28 changes: 28 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2220,5 +2220,33 @@
"contactSupport": "Contact Support",
"dismiss": "Dismiss"
}
},
"points": {
"title": "Valora Points",
"activity": "Activity",
"infoCard": {
"title": "More ways to earn points coming soon 🚀",
"body": "We are working on adding more ways to earn points through using the app. Keep checking in for updates."
},
"activitySection": {
"title": "How do I earn points?",
"body": "It's easy! Earn points by simply using the app."
},
"activityCards": {
"createWallet": {
"title": "Create a {{appName}} Wallet"
},
"swap": {
"title": "Swap assets",
"bottomSheet": {
"title": "Swap assets",
"body": "This space will be used to give all the details needed for this. Maybe minimum amount to swap. \n\nEarn {{pointsValue}} points whenever you swap an asset using {{appName}}.",
"cta": "Go to swap"
}
},
"moreComing": {
"title": "More coming soon!"
}
}
}
}
7 changes: 7 additions & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,10 @@ export enum JumpstartEvents {
jumpstart_claim_error_dismissed = 'jumpstart_claim_error_dismissed',
jumpstart_claim_error_contact_support = 'jumpstart_claim_error_contact_support',
}

export enum PointsEvents {
points_screen_open = 'points_screen_open',
points_screen_back = 'points_screen_back',
points_screen_card_press = 'points_screen_card_press',
points_screen_card_cta_press = 'points_screen_card_cta_press',
}
16 changes: 15 additions & 1 deletion src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
TransactionEvents,
WalletConnectEvents,
WebViewEvents,
PointsEvents,
} from 'src/analytics/Events'
import {
BackQuizProgress,
Expand Down Expand Up @@ -72,6 +73,7 @@ import { Field } from 'src/swap/types'
import { TokenDetailsActionName } from 'src/tokens/types'
import { NetworkId, TokenTransactionTypeV2, TransactionStatus } from 'src/transactions/types'
import { Currency } from 'src/utils/currencies'
import { PointsActivity } from 'src/points/types'

type Web3LibraryProps = { web3Library: 'contract-kit' | 'viem' }

Expand Down Expand Up @@ -1553,6 +1555,17 @@ interface JumpstartEventsProperties {
[JumpstartEvents.jumpstart_claim_error_contact_support]: undefined
}

interface PointsEventsProperties {
[PointsEvents.points_screen_open]: undefined
[PointsEvents.points_screen_back]: undefined
[PointsEvents.points_screen_card_press]: {
activity: PointsActivity
}
[PointsEvents.points_screen_card_cta_press]: {
activity: PointsActivity
}
}

export type AnalyticsPropertiesList = AppEventsProperties &
HomeEventsProperties &
SettingsEventsProperties &
Expand Down Expand Up @@ -1587,6 +1600,7 @@ export type AnalyticsPropertiesList = AppEventsProperties &
NftsEventsProperties &
BuilderHooksProperties &
DappShortcutsProperties &
TransactionDetailsProperties
TransactionDetailsProperties &
PointsEventsProperties

export type AnalyticsEventType = keyof AnalyticsPropertiesList
7 changes: 7 additions & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
TransactionEvents,
WalletConnectEvents,
WebViewEvents,
PointsEvents,
} from 'src/analytics/Events'

/**
Expand Down Expand Up @@ -464,6 +465,12 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[RewardsEvents.learn_more_pressed]: ``,
[RewardsEvents.claimed_reward]: ``,

// Events related to Valora Points program
[PointsEvents.points_screen_open]: `when Points home screen is opened`,
[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`,

// Events related to WalletConnect pairing (technical: opening up the communication channel via QR code or deeplink)
[WalletConnectEvents.wc_pairing_start]: `when WC pairing is started (no UI at this point)`,
[WalletConnectEvents.wc_pairing_success]: `when WC pairing succeeds`,
Expand Down
30 changes: 30 additions & 0 deletions src/components/PointsButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import React from 'react'
import { StyleProp, ViewStyle } from 'react-native'
import { PointsEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import AttentionIcon from 'src/icons/Attention'
import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { TopBarIconButton } from 'src/navigator/TopBarButton'

interface Props {
style?: StyleProp<ViewStyle>
size?: number
testID?: string
}

export default function PointsButton({ style, size, testID }: Props) {
const onPress = () => {
ValoraAnalytics.track(PointsEvents.points_screen_open)
navigate(Screens.PointsHome)
}

return (
<TopBarIconButton
testID={testID}
icon={<AttentionIcon size={size} />}
onPress={onPress}
style={style}
/>
)
}
3 changes: 3 additions & 0 deletions src/home/WalletHome.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import TransactionFeed from 'src/transactions/feed/TransactionFeed'
import { hasGrantedContactsPermission } from 'src/utils/contacts'
import PointsButton from 'src/components/PointsButton'

const AnimatedSectionList = Animated.createAnimatedComponent(SectionList)

Expand Down Expand Up @@ -213,8 +214,10 @@ function WalletHome({ navigation, route }: Props) {
const showBetaTag = getFeatureGate(StatsigFeatureGates.SHOW_BETA_TAG)
const topLeftElement = showBetaTag && <BetaTag />

const showPoints = getFeatureGate(StatsigFeatureGates.SHOW_POINTS)
const topRightElements = (
<View style={styles.topRightElementsContainer}>
{showPoints && <PointsButton testID={'WalletHome/PointsButton'} />}
<QrScanButton testID={'WalletHome/QRScanButton'} />
<NotificationBell testID={'WalletHome/NotificationBell'} />
</View>
Expand Down
18 changes: 18 additions & 0 deletions src/icons/Celebration.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react'
import Colors from 'src/styles/colors'
import Svg, { Path } from 'svgs'

interface Props {
size?: number
color?: string
}

const Celebration = ({ size = 24, color = Colors.black }: Props) => (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<Path
fill={color}
d="M0 21 5 7l9 9-14 5Zm3.3-3.3 7.05-2.5-4.55-4.55-2.5 7.05Zm9.25-6.15L11.5 10.5l5.6-5.6a2.624 2.624 0 0 1 1.925-.8c.75 0 1.392.267 1.925.8l.6.6-1.05 1.05-.6-.6a1.187 1.187 0 0 0-.875-.35c-.35 0-.642.117-.875.35l-5.6 5.6Zm-4-4L7.5 6.5l.6-.6c.233-.233.35-.517.35-.85 0-.333-.117-.617-.35-.85l-.65-.65L8.5 2.5l.65.65c.533.533.8 1.167.8 1.9 0 .733-.267 1.367-.8 1.9l-.6.6Zm2 2L9.5 8.5l3.6-3.6c.233-.233.35-.525.35-.875s-.117-.642-.35-.875l-1.6-1.6L12.55.5l1.6 1.6c.533.533.8 1.175.8 1.925s-.267 1.392-.8 1.925l-3.6 3.6Zm4 4L13.5 12.5l1.6-1.6a2.624 2.624 0 0 1 1.925-.8c.75 0 1.392.267 1.925.8l1.6 1.6-1.05 1.05-1.6-1.6a1.187 1.187 0 0 0-.875-.35c-.35 0-.642.117-.875.35l-1.6 1.6Z"
/>
</Svg>
)
export default Celebration
3 changes: 3 additions & 0 deletions src/icons/Checkmark.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface Props {
height?: number
width?: number
color?: string
stroke?: boolean
testID?: string
}

Expand All @@ -14,6 +15,7 @@ export default class Checkmark extends React.PureComponent<Props> {
height: 32,
width: 32,
color: colors.primary,
stroke: false,
testID: undefined,
}

Expand All @@ -29,6 +31,7 @@ export default class Checkmark extends React.PureComponent<Props> {
<Path
d="M20.5 7.33 9.186 18.643 4 13.458l1.33-1.33 3.856 3.847L19.17 6 20.5 7.33Z"
fill={this.props.color}
stroke={this.props.stroke ? this.props.color : undefined}
/>
</Svg>
)
Expand Down
18 changes: 18 additions & 0 deletions src/icons/Rocket.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react'
import Colors from 'src/styles/colors'
import Svg, { Path } from 'svgs'

interface Props {
size?: number
color?: string
}

const Rocket = ({ size = 24, color = Colors.black }: Props) => (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<Path
fill={color}
d="m3.65 8.025 1.95.825c.233-.467.475-.917.725-1.35.25-.433.525-.867.825-1.3l-1.4-.275-2.1 2.1ZM7.2 10.1l2.85 2.825c.7-.267 1.45-.675 2.25-1.225s1.55-1.175 2.25-1.875a13.455 13.455 0 0 0 2.738-3.887c.658-1.425.945-2.738.862-3.938-1.2-.083-2.517.204-3.95.863A13.4 13.4 0 0 0 10.3 5.6c-.7.7-1.325 1.45-1.875 2.25S7.467 9.4 7.2 10.1Zm4.45-1.625a1.92 1.92 0 0 1-.575-1.412c0-.559.192-1.03.575-1.413.383-.383.858-.575 1.425-.575.567 0 1.042.192 1.425.575.383.383.575.854.575 1.413a1.92 1.92 0 0 1-.575 1.412c-.383.383-.858.575-1.425.575-.567 0-1.042-.192-1.425-.575Zm.475 8.025 2.1-2.1-.275-1.4c-.433.3-.867.57-1.3.813-.433.241-.883.479-1.35.712l.825 1.975ZM19.95.175c.317 2.017.12 3.98-.587 5.888-.709 1.908-1.93 3.729-3.663 5.462L16.2 14c.067.333.05.658-.05.975-.1.317-.267.592-.5.825l-4.2 4.2-2.1-4.925L5.075 10.8.15 8.7l4.175-4.2c.233-.233.512-.4.837-.5.325-.1.655-.117.988-.05l2.475.5c1.733-1.733 3.55-2.958 5.45-3.675 1.9-.717 3.858-.917 5.875-.6Zm-18.025 13.8c.583-.583 1.296-.88 2.137-.887.842-.009 1.555.279 2.138.862s.87 1.296.862 2.138c-.008.841-.304 1.554-.887 2.137-.417.417-1.113.775-2.088 1.075-.975.3-2.32.567-4.037.8.233-1.717.5-3.062.8-4.037.3-.975.658-1.671 1.075-2.088Zm1.425 1.4c-.167.167-.333.47-.5.913a6.463 6.463 0 0 0-.35 1.337c.45-.067.896-.18 1.337-.337.442-.159.746-.321.913-.488.2-.2.308-.442.325-.725a.907.907 0 0 0-.275-.725.946.946 0 0 0-.725-.287 1.033 1.033 0 0 0-.725.312Z"
/>
</Svg>
)
export default Rocket
18 changes: 18 additions & 0 deletions src/icons/SwapArrows.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react'
import Colors from 'src/styles/colors'
import Svg, { Path } from 'svgs'

interface Props {
size?: number
color?: string
}

const SwapArrows = ({ size = 24, color = Colors.black }: Props) => (
<Svg width={size} height={size} viewBox="0 0 24 24" fill="none">
<Path
fill={color}
d="M11.722 15.567v.5h2.624l-3.235 3.227-3.235-3.227H10.5v-7.79h1.222v7.29ZM3.833 4.433v-.5H1.21L4.444.706 7.68 3.933H5.056v7.79H3.833v-7.29Z"
/>
</Svg>
)
export default SwapArrows
19 changes: 13 additions & 6 deletions src/navigator/Headers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import { Spacing } from 'src/styles/styles'
import { useTokenInfoByCurrency } from 'src/tokens/hooks'
import { TokenBalance } from 'src/tokens/slice'
import { Currency } from 'src/utils/currencies'
import { getFeatureGate } from 'src/statsig'
import { StatsigFeatureGates } from 'src/statsig/types'
import PointsButton from 'src/components/PointsButton'

export const noHeader: NativeStackNavigationOptions = {
headerShown: false,
Expand Down Expand Up @@ -282,12 +285,16 @@ export function HeaderTitleWithSubtitle({

export const tabHeader: NativeStackNavigationOptions = {
...emptyHeader,
headerRight: () => (
<View style={[styles.topElementsContainer, { marginRight: Spacing.Tiny4 }]}>
<QrScanButton testID="WalletHome/QRScanButton" />
<NotificationBell testID="WalletHome/NotificationBell" />
</View>
),
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>
)
},
headerLeft: () => (
<View style={[styles.topElementsContainer, { marginLeft: Spacing.Tiny4 }]}>
<AccountCircleButton testID="WalletHome/AccountCircle" />
Expand Down
7 changes: 7 additions & 0 deletions src/navigator/Navigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ import VerificationStartScreen from 'src/verify/VerificationStartScreen'
import WalletConnectSessionsScreen from 'src/walletConnect/screens/Sessions'
import WalletConnectRequest from 'src/walletConnect/screens/WalletConnectRequest'
import WebViewScreen from 'src/webview/WebViewScreen'
import PointsHome from 'src/points/PointsHome'

const TAG = 'Navigator'

Expand Down Expand Up @@ -570,6 +571,11 @@ const assetScreens = (Navigator: typeof Stack) => (
</>
)

const pointsScreens = (Navigator: typeof Stack) => (
<>
<Navigator.Screen name={Screens.PointsHome} component={PointsHome} options={noHeader} />
</>
)
const mapStateToProps = (state: RootState) => {
return {
choseToRestoreAccount: state.account.choseToRestoreAccount,
Expand Down Expand Up @@ -638,6 +644,7 @@ export function MainStackScreen() {
{swapScreens(Stack)}
{nftScreens(Stack)}
{assetScreens(Stack)}
{pointsScreens(Stack)}
</Stack.Navigator>
)
}
Expand Down
1 change: 1 addition & 0 deletions src/navigator/Screens.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ export enum Screens {
OnboardingSuccessScreen = 'OnboardingSuccessScreen',
PincodeEnter = 'PincodeEnter',
PincodeSet = 'PincodeSet',
PointsHome = 'PointsHome',
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 @@ -219,6 +219,7 @@ export type StackParamList = {
showGuidedOnboarding?: boolean
}
| undefined
[Screens.PointsHome]: undefined
[Screens.ProtectWallet]: undefined
[Screens.OnboardingRecoveryPhrase]: undefined
[Screens.Profile]: undefined
Expand Down
Loading

0 comments on commit 8278f6c

Please sign in to comment.