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

Welcome rewards flow #4279

Merged
merged 12 commits into from
Jul 10, 2024
Binary file added src/assets/welcome_offer.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
34 changes: 33 additions & 1 deletion src/components/common/buttons/TaskButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import { fireEvent } from '../../../lib/analytics/analytics'
import { theme } from '../../theme/styles'
import { openLink } from '../../../lib/utils/linking'
import { isWeb } from '../../../lib/utils/platform'
import { POST_CLAIM_CTA } from '../../../lib/analytics/constants'
import { MIGRATION_ACCEPTED, MIGRATION_DENIED, POST_CLAIM_CTA } from '../../../lib/analytics/constants'
import AsyncStorage from '../../../lib/utils/asyncStorage'

const TaskButton = ({ buttonText, url, eventTag }) => {
const goToTask = () => {
Expand All @@ -29,4 +30,35 @@ const TaskButton = ({ buttonText, url, eventTag }) => {
)
}

export const WalletV2Continue = ({ buttonText, dontShowAgain, onDismiss, promoUrl }) => {
const goToWalletV2 = async () => {
try {
if (dontShowAgain) {
fireEvent(MIGRATION_DENIED)
await AsyncStorage.setItem('dontShowWelcomeOffer', true)
return
}

fireEvent(MIGRATION_ACCEPTED)
openLink(promoUrl, '_blank')
} finally {
onDismiss()
}
}

return (
<CustomButton
styles={{ ...(!isWeb && { width: 250 }) }}
style={{ minWidth: 70, height: 40, minHeight: 32 }}
color={theme.colors.primary}
textStyle={{ fontSize: 16, color: theme.colors.white }}
onPress={goToWalletV2}
textColor={theme.colors.white}
withoutDone
>
{buttonText}
</CustomButton>
)
}

export default TaskButton
124 changes: 124 additions & 0 deletions src/components/common/dialogs/WelcomeOffer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import React, { useState } from 'react'
import { noop } from 'lodash'

import { t } from '@lingui/macro'
import { Image, Text, View } from 'react-native'

import ExplanationDialog from '../../common/dialogs/ExplanationDialog'

import { withStyles } from '../../../lib/styles'
import { WalletV2Continue } from '../../common/buttons/TaskButton'
import WelcomeBilly from '../../../assets/welcome_offer.png'

const mapStylesToProps = ({ theme }) => ({
container: {
marginTop: 0,
marginBottom: 0,
},
title: {
color: theme.colors.lightGdBlue,
fontSize: 30,
},
text: {
fontSize: 16,
lineHeight: 22,
},
button: {
width: '100%',
},
innerContainer: {
width: '100%',
flexDirection: 'column',
justifyContent: 'space-between',
paddingVertical: 12,
alignItems: 'center',
},
imageContainer: {
paddingVertical: 12,
},
image: {
width: 121,
height: 91,
},
titleContainer: {
paddingVertical: 12,
},
rewardText: {
color: '#00AEFF',
fontWeight: 700,
fontSize: 16,
},
rewardContainer: {
paddingBottom: 12,
flexDirection: 'row',
alignItems: 'center',
},
rewardAmountText: {
fontSize: 36,
fontWeight: 800,
color: '#00AEFF',
},
rewardAmountCurrency: {
fontSize: 24,
fontWeight: 700,
color: '#00AEFF',
},
description: {
paddingVertical: 12,
color: '#525252',
fontSize: 16,
},
descriptionText: {
paddingVertical: 12,
},
})

const WelcomeOffer = ({ styles, onDismiss = noop, ...dialogProps }) => {
const [dontShowAgain, setDontShow] = useState(false)

return (
<ExplanationDialog
{...dialogProps}
title={t`Special Offer: Try the new GoodWallet`}
titleStyle={styles.title}
containerStyle={styles.container}
resizeMode={false}
>
<View style={styles.innerContainer}>
<View style={styles.imageContainer}>
<Image source={WelcomeBilly} resizeMode={'contain'} style={styles.image} />
</View>

<Text style={styles.rewardText}>{t`Welcome Reward After First Claim`}</Text>
<View style={styles.rewardContainer}>
<Text style={[styles.rewardAmountText, { fontWeight: 'bold' }]}>{`200`}</Text>
<Text style={styles.rewardAmountCurrency}>{`G$`}</Text>
</View>
<Text style={styles.descriptionText}>
{t`Test out the new GoodWallet! For a limited time, you are eligible for `}{' '}
<b>{dialogProps.offerAmount} G$</b>{' '}
{t`bonus once you’ve made your first claim in the new GoodWallet.

Make sure you use the same login method you use here!
Not sure about your login method? You can see it in your Profile. `}
</Text>
</View>
<View marginTop={40} marginBottom="24">
<label style={{ marginBottom: 24, display: 'flex', alignItems: 'center' }}>
<input type="checkbox" onClick={() => setDontShow(prev => !prev)} style={{ width: 24, height: 24 }} />
<Text style={[styles.descriptionText, { paddingLeft: 8, userSelect: 'none' }]}>
{t`Dont show this offer again`}
</Text>
</label>
<WalletV2Continue
buttonText={t`CONTINUE`}
dontShowAgain={dontShowAgain}
onDismiss={onDismiss}
promoUrl={dialogProps.promoUrl}
/>
</View>
</ExplanationDialog>
)
}

export default withStyles(mapStylesToProps)(WelcomeOffer)
43 changes: 41 additions & 2 deletions src/components/dashboard/Dashboard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { useDebouncedCallback } from 'use-debounce'
import Mutex from 'await-mutex'
import { t } from '@lingui/macro'
import { WalletChatWidget } from 'react-native-wallet-chat'
import moment from 'moment'

import AsyncStorage from '../../lib/utils/asyncStorage'
import { normalizeByLength } from '../../lib/utils/normalizeText'
import { useDialog } from '../../lib/dialog/useDialog'
Expand All @@ -14,7 +16,7 @@ import { openLink } from '../../lib/utils/linking'
import { getRouteParams, lazyScreens, withNavigationOptions } from '../../lib/utils/navigation'
import { decimalsToFixed, supportsG$, supportsG$UBI, toMask } from '../../lib/wallet/utils'
import { formatWithAbbreviations, formatWithFixedValueDigits } from '../../lib/utils/formatNumber'
import { fireEvent, GOTO_TAB_FEED, SCROLL_FEED, SWITCH_NETWORK } from '../../lib/analytics/analytics'
import { fireEvent, GOTO_TAB_FEED, MIGRATION_INVITED, SCROLL_FEED, SWITCH_NETWORK } from '../../lib/analytics/analytics'
import {
GoodWalletContext,
TokenContext,
Expand Down Expand Up @@ -56,6 +58,7 @@ import WalletConnect from '../walletconnect/WalletConnectScan'
import useRefundDialog from '../refund/hooks/useRefundDialog'
import GoodActionBar from '../appNavigation/actionBar/components/GoodActionBar'
import { IconButton, Text } from '../../components/common'
import { retry } from '../../lib/utils/async'

import GreenCircle from '../../assets/ellipse46.svg'
import { useInviteCode } from '../invite/useInvites'
Expand Down Expand Up @@ -83,6 +86,7 @@ import SendToAddress from './SendToAddress'
import SendByQR from './SendByQR'
import SendLinkSummary from './SendLinkSummary'
import { ACTION_SEND } from './utils/sendReceiveFlow'
import WelcomeOffer from './../../components/common/dialogs/WelcomeOffer'

import GoodDollarPriceInfo from './GoodDollarPriceInfo/GoodDollarPriceInfo'
import Settings from './Settings'
Expand Down Expand Up @@ -301,7 +305,7 @@ const Dashboard = props => {
const [headerFullNameOpacityAnimValue] = useState(new Animated.Value(1))
const [topInfoHeight] = useState(new Animated.Value(240))
const [balanceTopAnimValue] = useState(new Animated.Value(0))
const { isDialogShown, showDialog, showErrorDialog } = useDialog()
const { hideDialog, isDialogShown, showDialog, showErrorDialog } = useDialog()
const showDeleteAccountDialog = useDeleteAccountDialog(showErrorDialog)
const [update, setUpdate] = useState(0)
const [showDelayedTimer, setShowDelayedTimer] = useState()
Expand All @@ -328,9 +332,11 @@ const Dashboard = props => {

const sendReceiveEnabled = useFeatureFlagOrDefault('send-receive-feature')
const dashboardButtonsEnabled = useFeatureFlagOrDefault('dashboard-buttons')
const showWelcomeOffer = useFlagWithPayload('show-welcome-offer')
const payload = useFlagWithPayload('claim-feature')

const { message: claimDisabledMessage, enabled: claimEnabled } = payload || {}
const { supportedCountries, enabled: welcomeOfferActive, promoUrl, offerAmount } = showWelcomeOffer || {}

const { securityEnabled, securityDialog } = useSecurityDialog()

Expand Down Expand Up @@ -521,6 +527,39 @@ const Dashboard = props => {
}
}, [isCitizen])

const dismissOffer = useCallback(async () => {
const today = moment().format('YYYY-MM-DD')
await AsyncStorage.setItem('shownOfferToday', today)
hideDialog()
}, [hideDialog])

useEffect(async () => {
const dontShowAgain = await AsyncStorage.getItem('dontShowWelcomeOffer')
const shownOfferToday = await AsyncStorage.getItem('shownOfferToday')
const today = moment().format('YYYY-MM-DD')

if (dontShowAgain || shownOfferToday === today) {
return
}

const country = await retry(fetch('https://get.geojs.io/v1/ip/country.json'), 3, 2000)
.then(_ => _.json())
.then(_ => _.country)

const isEligible = supportedCountries.split(',').includes(country)

if (isWeb && welcomeOfferActive && isEligible) {
L03TJ3 marked this conversation as resolved.
Show resolved Hide resolved
fireEvent(MIGRATION_INVITED)

showDialog({
content: <WelcomeOffer onDismiss={dismissOffer} promoUrl={promoUrl} offerAmount={offerAmount} />,
titleStyle: { paddingTop: 0, marginTop: 0, minHeight: 'auto' },
onDismiss: dismissOffer,
showButtons: false,
})
}
}, [welcomeOfferActive])

const animateClaim = useCallback(() => {
if (!entitlement || !supportsG$UBI(currentNetwork)) {
return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1087,12 +1087,11 @@ exports[`Dashboard matches snapshot 1`] = `
}
>
<div
className="css-view-1dbjc4n"
className="css-view-1dbjc4n r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down Expand Up @@ -1130,12 +1129,11 @@ exports[`Dashboard matches snapshot 1`] = `
className="css-view-1dbjc4n r-alignItems-1awozwy r-flexDirection-18u37iz r-justifyContent-1wtj0ep"
>
<div
className="css-view-1dbjc4n r-marginTop-14gqq1x"
className="css-view-1dbjc4n r-marginTop-14gqq1x r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down Expand Up @@ -1230,12 +1228,11 @@ exports[`Dashboard matches snapshot 1`] = `
}
>
<div
className="css-view-1dbjc4n"
className="css-view-1dbjc4n r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down Expand Up @@ -1273,12 +1270,11 @@ exports[`Dashboard matches snapshot 1`] = `
className="css-view-1dbjc4n r-alignItems-1awozwy r-flexDirection-18u37iz r-justifyContent-1wtj0ep"
>
<div
className="css-view-1dbjc4n r-marginTop-14gqq1x"
className="css-view-1dbjc4n r-marginTop-14gqq1x r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down Expand Up @@ -1373,12 +1369,11 @@ exports[`Dashboard matches snapshot 1`] = `
}
>
<div
className="css-view-1dbjc4n"
className="css-view-1dbjc4n r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down Expand Up @@ -1416,12 +1411,11 @@ exports[`Dashboard matches snapshot 1`] = `
className="css-view-1dbjc4n r-alignItems-1awozwy r-flexDirection-18u37iz r-justifyContent-1wtj0ep"
>
<div
className="css-view-1dbjc4n r-marginTop-14gqq1x"
className="css-view-1dbjc4n r-marginTop-14gqq1x r-width-1q42mak"
style={
{
"backgroundColor": "rgba(238,238,238,1.00)",
"height": "10px",
"width": "74px",
}
}
/>
Expand Down
4 changes: 2 additions & 2 deletions src/components/invite/Invite.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ const Invite = ({ screenProps, styles }) => {
const userStorage = useUserStorage()
const propSuffix = usePropSuffix()

const totalEarned = parseInt(get(inviteState, 'totalEarned', 0))
const totalEarned = parseInt(get(inviteState, 'totalEarned', undefined))
const bounty = decimalsToFixed(toDecimals(get(level, 'bounty', 0)))

const toggleHowTo = () => {
Expand All @@ -458,7 +458,7 @@ const Invite = ({ screenProps, styles }) => {
}
}, [inviteState, propSuffix])

if (isNil(bounty) || isNaN(bounty)) {
if (isNil(bounty) || isNaN(bounty) || isNaN(totalEarned)) {
return null
}

Expand Down
3 changes: 3 additions & 0 deletions src/lib/analytics/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,6 @@ export const REWARD_RECEIVED = 'REWARD_RECEIVED'
export const POST_CLAIM_CTA = 'POST_CLAIM_CTA'
export const COPY_ADDRESS = 'COPY_ADDRESS'
export const COPY_PRIVATE_KEY = 'COPY_PRIVATE_KEY'
export const MIGRATION_INVITED = 'migration_invited'
export const MIGRATION_ACCEPTED = 'migration_accepted'
export const MIGRATION_DENIED = 'migration_denied'
Loading
Loading