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

feat(onboarding): add terms and conditions colloquial variant #5293

Merged
merged 4 commits into from
Apr 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,17 @@
"privacy": "By joining this network, you give us permission to collect anonymous information about your use of the app. Additionally, if you connect your phone number, a hashed copy of it will be stored. If you grant Valora access to your contact list, Valora will import each contact's name, phone number and profile picture to allow users to connect through the Valora app. To learn how we collect and use this information please review our <0>Privacy Policy</0>.",
"goldDisclaimer": "When you create an \"account\" with Valora you are creating a digital wallet to which only you hold the keys. No other person or entity, including Valora, can recover your key, change or undo transactions, or recover lost funds. Be aware that digital assets are part of a new asset class and present a risk of financial loss. Carefully consider your financial circumstances and tolerance for financial risk before purchasing any digital asset."
},
"termsColloquial": {
"title": "Let’s start by creating your wallet",
"privacyHeading": "Your Info & Privacy:",
"privacy1": "We gather usage data which helps us improve the app and security. The type of information we collect, how we use it and your rights related to that information can all be seen in our <0>Privacy Policy</0>.",
"privacy2": "If you decide to link your phone number, we will store an encrypted copy of it.",
"privacy3": "If you decide to connect your contacts, we use their names, numbers, and profile pictures to make it easier to find them.",
"walletHeading": "Your Digital Wallet with Valora:",
"wallet1": "You’re about to create a digital wallet. Only you have the key to your wallet. We cannot recover your key or your assets if you lose your key. We also cannot reverse actions taken through Valora on blockchains.",
"wallet2": "Digital assets, the assets with which you will interact with Valora, come with unique risks. By using Valora, you accept these risks and take responsibility for them. Please consider your finances and risk tolerance before making choices.",
"fullTerms": "Read our full <0>Terms & Conditions</0>"
},
"fullNameOrPsuedonym": "Full name or pseudonym",
"namePlaceholder": "ex. name",
"nameAndPicGuideCopyTitle": "What’s your name?",
Expand Down
113 changes: 77 additions & 36 deletions src/onboarding/registration/RegulatoryTerms.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,47 +6,88 @@ import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { RegulatoryTerms as RegulatoryTermsClass } from 'src/onboarding/registration/RegulatoryTerms'
import { firstOnboardingScreen } from 'src/onboarding/steps'
import { getExperimentParams } from 'src/statsig'
import { createMockStore, getMockI18nProps } from 'test/utils'

jest.mock('src/navigator/NavigationService', () => {
return { navigate: jest.fn() }
})
jest.mock('src/onboarding/steps')
jest.mock('src/statsig')

describe('RegulatoryTermsScreen', () => {
describe('when accept button is pressed', () => {
it('stores that info', async () => {
const store = createMockStore({})
const acceptTerms = jest.fn()
const wrapper = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)
fireEvent.press(wrapper.getByTestId('AcceptTermsButton'))
expect(acceptTerms).toHaveBeenCalled()
})
it('navigates to PincodeSet', () => {
const store = createMockStore({})
const acceptTerms = jest.fn()
jest.mocked(firstOnboardingScreen).mockReturnValue(Screens.PincodeSet)
const acceptTerms = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
})
it('renders correct components for control', () => {
jest.mocked(getExperimentParams).mockReturnValue({ variant: 'control' })
const store = createMockStore({})
const { getByTestId, queryByTestId } = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)

const wrapper = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)
fireEvent.press(wrapper.getByTestId('AcceptTermsButton'))
expect(firstOnboardingScreen).toHaveBeenCalled()
expect(navigate).toHaveBeenCalledWith(Screens.PincodeSet)
})
expect(getByTestId('scrollView')).toBeTruthy()
expect(queryByTestId('colloquialTermsSectionList')).toBeFalsy()
})

it('renders correct components for colloquial_terms', () => {
jest.mocked(getExperimentParams).mockReturnValue({ variant: 'colloquial_terms' })
const store = createMockStore({})
const { getByTestId, queryByTestId } = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)

expect(getByTestId('colloquialTermsSectionList')).toBeTruthy()
expect(queryByTestId('scrollView')).toBeFalsy()
})

describe.each([{ variant: 'control' }, { variant: 'colloquial_terms' }])(
'when accept button is pressed ($variant)',
({ variant }) => {
beforeAll(() => {
jest.mocked(getExperimentParams).mockReturnValue({ variant })
})
it('stores that info', async () => {
const store = createMockStore({})
const wrapper = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)
fireEvent.press(wrapper.getByTestId('AcceptTermsButton'))
expect(acceptTerms).toHaveBeenCalled()
})
it('navigates to PincodeSet', () => {
const store = createMockStore({})
jest.mocked(firstOnboardingScreen).mockReturnValue(Screens.PincodeSet)

const wrapper = render(
<Provider store={store}>
<RegulatoryTermsClass
{...getMockI18nProps()}
acceptTerms={acceptTerms}
recoveringFromStoreWipe={false}
/>
</Provider>
)
fireEvent.press(wrapper.getByTestId('AcceptTermsButton'))
expect(firstOnboardingScreen).toHaveBeenCalled()
expect(navigate).toHaveBeenCalledWith(Screens.PincodeSet)
})
}
)
})
150 changes: 125 additions & 25 deletions src/onboarding/registration/RegulatoryTerms.tsx
MuckT marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react'
import { Trans, WithTranslation } from 'react-i18next'
import { Platform, ScrollView, StyleSheet, Text } from 'react-native'
import { Platform, ScrollView, SectionList, StyleSheet, Text, View } from 'react-native'
import { SafeAreaInsetsContext, SafeAreaView } from 'react-native-safe-area-context'
import { connect } from 'react-redux'
import { acceptTerms } from 'src/account/actions'
Expand All @@ -17,8 +17,12 @@ import { navigate } from 'src/navigator/NavigationService'
import { Screens } from 'src/navigator/Screens'
import { firstOnboardingScreen } from 'src/onboarding/steps'
import { RootState } from 'src/redux/reducers'
import { getExperimentParams } from 'src/statsig'
import { ExperimentConfigs } from 'src/statsig/constants'
import { StatsigExperiments } from 'src/statsig/types'
import Colors from 'src/styles/colors'
import fontStyles from 'src/styles/fonts'
import fontStyles, { typeScale } from 'src/styles/fonts'
import { Spacing } from 'src/styles/styles'
import { navigateToURI } from 'src/utils/linking'

const MARGIN = 24
Expand Down Expand Up @@ -71,33 +75,108 @@ export class RegulatoryTerms extends React.Component<Props> {
navigateToURI(PRIVACY_LINK)
}

render() {
renderTerms() {
const { t } = this.props

return (
<SafeAreaView style={styles.container}>
<DevSkipButton nextScreen={Screens.PincodeSet} />
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
testID="scrollView"
>
<Logo color={Colors.black} size={32} />
<Text style={styles.title}>{t('terms.title')}</Text>
<Text style={styles.disclaimer}>
<Trans i18nKey={'terms.info'}>
<Text onPress={this.onPressGoToTerms} style={styles.disclaimerLink} />
</Trans>
</Text>
<Text style={styles.header}>{t('terms.heading1')}</Text>
<Text style={styles.disclaimer}>
<Trans i18nKey={'terms.privacy'}>
<Text onPress={this.onPressGoToPrivacyPolicy} style={styles.disclaimerLink} />
<ScrollView
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
testID="scrollView"
>
<Logo color={Colors.black} size={32} />
<Text style={styles.title}>{t('terms.title')}</Text>
<Text style={styles.disclaimer}>
<Trans i18nKey={'terms.info'}>
<Text onPress={this.onPressGoToTerms} style={styles.link} />
</Trans>
</Text>
<Text style={styles.header}>{t('terms.heading1')}</Text>
<Text style={styles.disclaimer}>
<Trans i18nKey={'terms.privacy'}>
<Text onPress={this.onPressGoToPrivacyPolicy} style={styles.link} />
</Trans>
</Text>
<Text style={styles.header}>{t('terms.heading2')}</Text>
<Text style={styles.disclaimer}>{t('terms.goldDisclaimer')}</Text>
</ScrollView>
)
}

renderColloquialTerms() {
const { t } = this.props

return (
<SectionList
style={styles.scrollView}
contentContainerStyle={styles.scrollContent}
testID="colloquialTermsSectionList"
sections={[
{
title: t('termsColloquial.privacyHeading'),
data: [
{ text: 'termsColloquial.privacy1', onPress: this.onPressGoToPrivacyPolicy },
{ text: 'termsColloquial.privacy2' },
{ text: 'termsColloquial.privacy3' },
],
},
{
title: t('termsColloquial.walletHeading'),
data: [{ text: 'termsColloquial.wallet1' }, { text: 'termsColloquial.wallet2' }],
},
]}
renderItem={({ item }) => {
return (
<View style={styles.itemContainer}>
<Text style={styles.item}>{'\u2022'}</Text>
{item.onPress ? (
<Text style={styles.item}>
<Trans i18nKey={item.text}>
<Text onPress={item.onPress} style={styles.link} />
</Trans>
</Text>
) : (
<Text style={styles.item}>
<Trans i18nKey={item.text} />
</Text>
)}
</View>
)
}}
renderSectionHeader={({ section: { title } }) => (
<Text style={styles.sectionHeader}>{title}</Text>
)}
ListHeaderComponent={
<Text style={styles.titleColloquial}>{t('termsColloquial.title')}</Text>
}
ListFooterComponent={
<Text style={styles.fullTerms}>
<Trans i18nKey="termsColloquial.fullTerms">
<Text onPress={this.onPressGoToTerms} style={styles.link} />
</Trans>
</Text>
<Text style={styles.header}>{t('terms.heading2')}</Text>
<Text style={styles.disclaimer}>{t('terms.goldDisclaimer')}</Text>
</ScrollView>
}
stickySectionHeadersEnabled={false}
/>
)
}

render() {
const { t } = this.props

const { variant } = getExperimentParams(
ExperimentConfigs[StatsigExperiments.ONBOARDING_TERMS_AND_CONDITIONS]
)

return (
<SafeAreaView
style={styles.container}
// don't apply safe area padding to top on iOS since it is opens like a
// bottom sheet (modal animated screen)
edges={Platform.select({ ios: ['bottom', 'left', 'right'] })}
>
<DevSkipButton nextScreen={Screens.PincodeSet} />
{variant === 'colloquial_terms' ? this.renderColloquialTerms() : this.renderTerms()}
<SafeAreaInsetsContext.Consumer>
{(insets) => (
<Button
Expand Down Expand Up @@ -144,11 +223,32 @@ const styles = StyleSheet.create({
...fontStyles.small,
marginBottom: 15,
},
disclaimerLink: {
link: {
textDecorationLine: 'underline',
},
button: {
marginTop: MARGIN,
marginHorizontal: MARGIN,
},
titleColloquial: {
...typeScale.titleSmall,
marginBottom: Spacing.Small12,
},
sectionHeader: {
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Small12,
},
itemContainer: {
flexDirection: 'row',
gap: Spacing.Smallest8,
},
item: {
...typeScale.bodySmall,
flexShrink: 1,
},
fullTerms: {
...typeScale.labelSemiBoldSmall,
marginVertical: Spacing.Small12,
color: Colors.infoDark,
},
})
25 changes: 23 additions & 2 deletions src/statsig/constants.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { StatsigDynamicConfigs, StatsigExperiments, StatsigFeatureGates } from 'src/statsig/types'
import {
StatsigDynamicConfigs,
StatsigExperiments,
StatsigFeatureGates,
StatsigParameter,
} from 'src/statsig/types'
import { NetworkId } from 'src/transactions/types'
import networkConfig from 'src/web3/networkConfig'

Expand All @@ -24,7 +29,7 @@ export const FeatureGates = {
[StatsigFeatureGates.SHOW_JUMPSTART_SEND]: false,
[StatsigFeatureGates.USE_TAB_NAVIGATOR]: false,
[StatsigFeatureGates.SHOW_POINTS]: false,
}
} satisfies { [key in StatsigFeatureGates]: boolean }

export const ExperimentConfigs = {
// NOTE: the keys of defaultValues MUST be parameter names
Expand All @@ -46,6 +51,17 @@ export const ExperimentConfigs = {
skipVerification: false,
},
},
[StatsigExperiments.ONBOARDING_TERMS_AND_CONDITIONS]: {
experimentName: StatsigExperiments.ONBOARDING_TERMS_AND_CONDITIONS,
defaultValues: {
variant: 'control' as 'control' | 'colloquial_terms' | 'checkbox',
},
},
} satisfies {
[key in StatsigExperiments]: {
experimentName: key
defaultValues: { [key: string]: StatsigParameter }
}
}

export const DynamicConfigs = {
Expand Down Expand Up @@ -119,4 +135,9 @@ export const DynamicConfigs = {
rewardReminderDate: new Date(0).toISOString(),
},
},
} satisfies {
[key in StatsigDynamicConfigs]: {
configName: key
defaultValues: { [key: string]: StatsigParameter }
}
}
2 changes: 1 addition & 1 deletion src/statsig/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ export enum StatsigFeatureGates {
}

export enum StatsigExperiments {
SWAPPING_NON_NATIVE_TOKENS = 'swapping_non_native_tokens',
DAPP_RANKINGS = 'dapp_rankings',
SWAP_BUY_AMOUNT = 'swap_buy_amount',
ONBOARDING_PHONE_VERIFICATION = 'onboarding_phone_verification',
ONBOARDING_TERMS_AND_CONDITIONS = 'onboarding_terms_and_conditions',
}

export type StatsigParameter =
Expand Down
Loading