Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(points): use points config from redux rather than Statsig #5232

Merged
merged 43 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
830368f
Add initial points activity feed
jophish Apr 4, 2024
618bdac
Remove unrelated change
jophish Apr 4, 2024
268cb57
Remove bottom sheet for now
jophish Apr 4, 2024
a4397f9
Merge branch 'main' into jophish/ret-1032
jophish Apr 4, 2024
3072fb2
Undo more stuff
jophish Apr 4, 2024
4ead19e
Schema
jophish Apr 4, 2024
fe8a966
Schema
jophish Apr 4, 2024
72cda37
Schema
jophish Apr 4, 2024
ee99ca0
knip
jophish Apr 4, 2024
d8e914a
Update saga
jophish Apr 11, 2024
3bb8395
Merge branch 'main' into jophish/ret-1032
jophish Apr 11, 2024
352f3b8
Fix imports
jophish Apr 11, 2024
e51b8b0
Schema
jophish Apr 11, 2024
353ccee
Fix test
jophish Apr 11, 2024
0a8841e
Merge branch 'main' into jophish/ret-1032
kathaypacific Apr 12, 2024
5a78c55
feat(points): fetch points config from getPointsConfig function
kathaypacific Apr 12, 2024
e5bf384
chore: redux migrations
kathaypacific Apr 12, 2024
c228077
chore: revert unrelated changes
kathaypacific Apr 12, 2024
c6223ae
chore: remove success state
kathaypacific Apr 12, 2024
0eb08b6
fix: root schema
kathaypacific Apr 12, 2024
7307c5a
chore: remove unused selector
kathaypacific Apr 12, 2024
9cc30bf
chore: rename selector
kathaypacific Apr 12, 2024
f087f03
chore(points): use points config from redux rather than Statsig
kathaypacific Apr 12, 2024
6f9e498
Merge branch 'main' into kathy/points-config-redux
kathaypacific Apr 15, 2024
0e26248
fix: bad merge
kathaypacific Apr 15, 2024
fbbe8e2
Merge branch 'kathy/points-config-redux' into kathy/points-config-switch
kathaypacific Apr 15, 2024
02f434a
fix: first dispatch
kathaypacific Apr 15, 2024
80a177b
Merge branch 'kathy/points-config-redux' into kathy/points-config-switch
kathaypacific Apr 15, 2024
5ac7dc6
fix: redundant action
kathaypacific Apr 15, 2024
a58d881
Merge branch 'kathy/points-config-redux' into kathy/points-config-switch
kathaypacific Apr 15, 2024
4f026a4
chore: revert unnecessary changes
kathaypacific Apr 15, 2024
04f4438
chore: rename points to pointsAmount in redux
kathaypacific Apr 15, 2024
95b1cf0
chore: remove null type
kathaypacific Apr 15, 2024
d5139ed
chore: remove error scenario
kathaypacific Apr 15, 2024
b89ed4c
chore: add back success state
kathaypacific Apr 15, 2024
ae8bdda
fix: test
kathaypacific Apr 16, 2024
52b7652
chore: clean up
kathaypacific Apr 16, 2024
028a34c
Merge branch 'main' into kathy/points-config-redux
kathaypacific Apr 17, 2024
269b177
Merge branch 'kathy/points-config-redux' into kathy/points-config-switch
kathaypacific Apr 17, 2024
a70b914
chore: improve naming
kathaypacific Apr 17, 2024
90812f1
fix: test
kathaypacific Apr 17, 2024
215833f
fix: lint
kathaypacific Apr 17, 2024
6254384
Merge branch 'main' into kathy/points-config-switch
kathaypacific Apr 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -679,4 +679,5 @@ export enum PointsEvents {
points_screen_back = 'points_screen_back',
points_screen_card_press = 'points_screen_card_press',
points_screen_card_cta_press = 'points_screen_card_cta_press',
points_screen_activity_press = 'points_screen_activity_press',
}
1 change: 1 addition & 0 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1576,6 +1576,7 @@ interface PointsEventsProperties {
[PointsEvents.points_screen_card_cta_press]: {
activity: PointsActivity
}
[PointsEvents.points_screen_activity_press]: undefined
}

export type AnalyticsPropertiesList = AppEventsProperties &
Expand Down
1 change: 1 addition & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,7 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[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`,
[PointsEvents.points_screen_activity_press]: `when the Activity button is pressed from 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)`,
Expand Down
43 changes: 20 additions & 23 deletions src/points/ActivityCardSection.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,21 @@
import React from 'react'
import { isPointsActivity } from 'src/points/types'
import ActivityCard from 'src/points/ActivityCard'
import { StatsigDynamicConfigs } from 'src/statsig/types'
import { getDynamicConfigParams } from 'src/statsig'
import { DynamicConfigs } from 'src/statsig/constants'
import { useTranslation } from 'react-i18next'
import { StyleSheet, Text, View } from 'react-native'
import ActivityCard from 'src/points/ActivityCard'
import { BottomSheetParams, PointsMetadata, isPointsActivity } from 'src/points/types'
import { Colors } from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { Colors } from 'src/styles/colors'
import { useTranslation } from 'react-i18next'
import { PointsMetadata } from 'src/points/types'
import { BottomSheetParams } from 'src/points/types'

interface Props {
onCardPress: (bottomSheetDetails: BottomSheetParams) => void
pointsSections: PointsMetadata[]
}

export default function ActivityCardSection({ onCardPress }: Props) {
export default function ActivityCardSection({ onCardPress, pointsSections }: Props) {
const { t } = useTranslation()

const pointsConfig = getDynamicConfigParams(DynamicConfigs[StatsigDynamicConfigs.POINTS_CONFIG])

function makeSection(pointsMetadata: PointsMetadata): React.ReactNode {
function makeSection(pointsMetadata: PointsMetadata, sectionNumber: number): React.ReactNode {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: is that an index? wdyt about naming it sectionIndex?

const points = pointsMetadata.points

const cards = pointsMetadata.activities
Expand All @@ -35,6 +29,18 @@ export default function ActivityCardSection({ onCardPress }: Props) {
/>
))

// add the "more coming" card to the last section
if (sectionNumber === pointsSections.length - 1) {
cards.push(
<ActivityCard
key="more-coming"
activity="more-coming"
points={points}
onPress={onCardPress}
/>
)
}

if (!cards.length) {
return <View key={points}></View>
}
Expand All @@ -53,22 +59,13 @@ export default function ActivityCardSection({ onCardPress }: Props) {
)
}

const sortedSections = pointsConfig.pointsMetadata
.sort((a, b) => {
if (a.points < b.points) return 1
if (a.points > b.points) return -1
return 0
})
.filter((metadata) => metadata.points)
.map(makeSection)

return (
<View style={styles.container}>
<View style={styles.textContainer}>
<Text style={styles.title}>{t('points.activitySection.title')}</Text>
<Text style={styles.body}>{t('points.activitySection.body')}</Text>
</View>
{sortedSections}
{pointsSections.map(makeSection)}
</View>
)
}
Expand Down
86 changes: 47 additions & 39 deletions src/points/PointsHome.test.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import * as React from 'react'
import { fireEvent, render, waitFor } from '@testing-library/react-native'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import * as 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 PointsHome from 'src/points/PointsHome'
import { Screens } from 'src/navigator/Screens'
import PointsHome from 'src/points/PointsHome'
import { getHistoryStarted } from 'src/points/slice'
import { createMockStore, getMockStackScreenProps } from 'test/utils'
import { PointsEvents } from 'src/analytics/Events'

jest.mock('src/statsig', () => ({
getDynamicConfigParams: jest.fn().mockReturnValue({
Expand Down Expand Up @@ -53,53 +54,64 @@ jest.mock('src/statsig', () => ({

const mockScreenProps = () => getMockStackScreenProps(Screens.PointsHome)

const renderPointsHome = () => {
const store = createMockStore({
points: {
pointsConfig: {
activitiesById: {
swap: {
points: 50,
},
'create-wallet': {
points: 20,
},
},
},
},
})
const tree = render(
<Provider store={store}>
<PointsHome {...mockScreenProps()} />
</Provider>
)

return {
store,
...tree,
}
}

describe(PointsHome, () => {
beforeEach(() => {
jest.clearAllMocks()
})

it('renders multiple sections', async () => {
const { getByTestId, queryByTestId } = render(
<Provider store={createMockStore()}>
<PointsHome {...mockScreenProps()} />
</Provider>
it('opens activity bottom sheet', async () => {
const { getByTestId, store } = renderPointsHome()

fireEvent.press(getByTestId('PointsActivityButton'))
await waitFor(() =>
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_screen_activity_press)
)
expect(store.getActions()).toEqual([getHistoryStarted({ fromPage: false })])
})

it('renders multiple sections', async () => {
const { getByTestId, queryByTestId } = renderPointsHome()

expect(getByTestId('PointsActivitySection-50')).toBeTruthy()
expect(getByTestId('PointsActivitySection-20')).toBeTruthy()

expect(getByTestId('PointsActivityCard-swap-50')).toBeTruthy()
expect(getByTestId('PointsActivityCard-more-coming-50')).toBeTruthy()
expect(getByTestId('PointsActivityCard-create-wallet-50')).toBeTruthy()
expect(queryByTestId('PointsActivityCard-create-wallet-50')).toBeFalsy()

expect(queryByTestId('PointsActivityCard-swap-20')).toBeFalsy()
expect(getByTestId('PointsActivityCard-more-coming-20')).toBeTruthy()
expect(getByTestId('PointsActivityCard-create-wallet-20')).toBeTruthy()
})

it('ignores unknown activities', async () => {
const { queryByTestId } = render(
<Provider store={createMockStore()}>
<PointsHome {...mockScreenProps()} />
</Provider>
)
expect(queryByTestId('PointsActivityCard-foo-50')).toBeFalsy()
})

it('ignores 0 point value activities', async () => {
const { queryByTestId } = render(
<Provider store={createMockStore()}>
<PointsHome {...mockScreenProps()} />
</Provider>
)
expect(queryByTestId('PointsActivitySection-0')).toBeFalsy()
})

it('opens Swap bottom sheet', async () => {
const { getByTestId } = render(
<Provider store={createMockStore()}>
<PointsHome {...mockScreenProps()} />
</Provider>
)
const { getByTestId } = renderPointsHome()
fireEvent.press(getByTestId('PointsActivityCard-swap-50'))
await waitFor(() =>
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_screen_card_press, {
Expand All @@ -109,11 +121,7 @@ describe(PointsHome, () => {
})

it('navigates to Swap screen on CTA press', async () => {
const { getByTestId } = render(
<Provider store={createMockStore()}>
<PointsHome {...mockScreenProps()} />
</Provider>
)
const { getByTestId } = renderPointsHome()
fireEvent.press(getByTestId('PointsActivityCard-swap-50'))
await waitFor(() =>
expect(ValoraAnalytics.track).toHaveBeenCalledWith(PointsEvents.points_screen_card_press, {
Expand Down
70 changes: 50 additions & 20 deletions src/points/PointsHome.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,38 @@
import { NativeStackScreenProps } from '@react-navigation/native-stack'
import { StackParamList } from 'src/navigator/types'
import BackButton from 'src/components/BackButton'
import React, { useEffect, useState, useRef } from 'react'
import React, { useEffect, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { ScrollView, StyleSheet, Text, View } from 'react-native'
import { Screens } from 'src/navigator/Screens'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { Colors } from 'src/styles/colors'
import { SafeAreaView } from 'react-native-safe-area-context'
import ActivityCardSection from 'src/points/ActivityCardSection'
import { PointsEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import BackButton from 'src/components/BackButton'
import BottomSheet, { BottomSheetRefType } from 'src/components/BottomSheet'
import Button, { BtnSizes, BtnTypes } from 'src/components/Button'
import { BottomSheetParams, PointsActivity } from 'src/points/types'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import { PointsEvents } from 'src/analytics/Events'
import CustomHeader from 'src/components/header/CustomHeader'
import { Screens } from 'src/navigator/Screens'
import { StackParamList } from 'src/navigator/types'
import ActivityCardSection from 'src/points/ActivityCardSection'
import { pointsSectionsSelector } from 'src/points/selectors'
import { getHistoryStarted } from 'src/points/slice'
import { BottomSheetParams, PointsActivity } from 'src/points/types'
import { useDispatch, useSelector } from 'src/redux/hooks'
import { Colors } from 'src/styles/colors'
import { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'

type Props = NativeStackScreenProps<StackParamList, Screens.PointsHome>

export default function PointsHome({ route, navigation }: Props) {
const { t } = useTranslation()

const dispatch = useDispatch()

const pointsSections = useSelector(pointsSectionsSelector)

// TODO: Use real points balance
const pointsBalance = 50

const bottomSheetRef = useRef<BottomSheetRefType>(null)
const activityCardBottomSheetRef = useRef<BottomSheetRefType>(null)

const [bottomSheetParams, setBottomSheetParams] = useState<BottomSheetParams | undefined>(
undefined
Expand All @@ -36,7 +43,7 @@ export default function PointsHome({ route, navigation }: Props) {

useEffect(() => {
if (bottomSheetParams) {
bottomSheetRef.current?.snapToIndex(0)
activityCardBottomSheetRef.current?.snapToIndex(0)
}
}, [bottomSheetParams])

Expand All @@ -48,28 +55,41 @@ export default function PointsHome({ route, navigation }: Props) {
onPress()
}
}
const onPressActivity = () => {
ValoraAnalytics.track(PointsEvents.points_screen_activity_press)
dispatch(getHistoryStarted({ fromPage: false }))
// TODO: Open history bottom sheet
}

return (
<SafeAreaView style={styles.outerContainer} edges={['top']}>
<CustomHeader
style={styles.header}
left={<BackButton eventName={PointsEvents.points_screen_back} />}
/>
<ScrollView contentContainerStyle={styles.contentContainer}>
<Text style={styles.title}>{t('points.title')}</Text>
<View style={styles.titleRow}>
<Text style={styles.title}>{t('points.title')}</Text>
<Button
testID={'PointsActivityButton'}
onPress={onPressActivity}
text={t('points.activity')}
type={BtnTypes.GRAY_WITH_BORDER}
fontStyle={typeScale.labelXSmall}
size={BtnSizes.FULL}
touchableStyle={styles.buttonStyle}
/>
</View>
<View style={styles.balanceRow}>
<Text style={styles.balance}>{pointsBalance}</Text>
</View>
<View style={styles.infoCard}>
<Text style={styles.infoCardTitle}>{t('points.infoCard.title')}</Text>
<Text style={styles.infoCardBody}>{t('points.infoCard.body')}</Text>
</View>
<ActivityCardSection onCardPress={onCardPress} />
<ActivityCardSection onCardPress={onCardPress} pointsSections={pointsSections} />
</ScrollView>
<BottomSheet
snapPoints={['50%']}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a personal preference but the vertical spaces triggered me, particularly between the bottom of the button and the bottom of the screen. i think it's better to let the bottom sheet size itself (if we need to increase the height, we should add space above the button)

forwardedRef={bottomSheetRef}
testId={`PointsActivityBottomSheet`}
>
<BottomSheet forwardedRef={activityCardBottomSheetRef} testId={`PointsActivityBottomSheet`}>
{bottomSheetParams && (
<>
<View style={styles.bottomSheetPointAmountContainer}>
Expand Down Expand Up @@ -152,4 +172,14 @@ const styles = StyleSheet.create({
...typeScale.titleMedium,
paddingVertical: Spacing.Smallest8,
},
titleRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
buttonStyle: {
height: undefined,
paddingHorizontal: Spacing.Small12,
paddingVertical: Spacing.Smallest8,
},
})