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.
21 changes: 21 additions & 0 deletions src/components/common/buttons/TaskButton.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,25 @@ const TaskButton = ({ buttonText, url, eventTag }) => {
)
}

export const WalletV2Continue = ({ buttonText }) => {
const goToWalletV2 = () => {
// fireEvent()
openLink('https://good-wallet-v2.vercel.app/en', '_blank')
}

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
107 changes: 107 additions & 0 deletions src/components/common/dialogs/WelcomeOffer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import React 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 }) => (
<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}>{`Welcome Reward After First Claim`}</Text>
<View style={styles.rewardContainer}>
<Text style={styles.rewardAmountText}>{`200`}</Text>
<Text style={styles.rewardAmountCurrency}>{`G$`}</Text>
</View>
<Text
style={styles.descriptionText}
>{`Test out the new GoodWallet! For a limited time, you are eligible for a 200 G$ bonus once you’ve made your first claim in the new GoodWallet.`}</Text>
sirpy marked this conversation as resolved.
Show resolved Hide resolved
</View>
<View>
{/* todo: find simplest checkbox solution for react-native */}
{/* <input type="checkbox" /> */}
{/* <button> Continue </button> */}
<WalletV2Continue buttonText="CONTINUE" />
</View>
</ExplanationDialog>
)

export default withStyles(mapStylesToProps)(WelcomeOffer)
7 changes: 6 additions & 1 deletion src/components/dashboard/SendLinkSummary.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { get } from 'lodash'
import { text } from 'react-native-communications'
import { t } from '@lingui/macro'
import { useBridge } from '@gooddollar/web3sdk-v2'
import { usePostHog } from 'posthog-react-native'
import { fireEvent, SEND_DONE } from '../../lib/analytics/analytics'
import { type TransactionEvent } from '../../lib/userStorage/UserStorageClass'
import { FeedItemType } from '../../lib/userStorage/FeedStorage'
Expand Down Expand Up @@ -44,6 +45,7 @@ const SendLinkSummary = ({ screenProps, styles }: AmountProps) => {
const { showDialog, hideDialog, showErrorDialog } = useDialog()

const { sendBridgeRequest, bridgeRequestStatus } = useBridge()
const posthog = usePostHog()

const [shared, setShared] = useState(false)
const [link, setLink] = useState('')
Expand Down Expand Up @@ -224,6 +226,8 @@ const SendLinkSummary = ({ screenProps, styles }: AmountProps) => {

userStorage.enqueueTX(transactionEvent)

posthog.capture('used_sendviaddress')

fireEvent(SEND_DONE, { type: get(screenState, 'type', contact ? 'contact' : 'address') }) //type can be qrCode, receive, contact, contactsms

showDialog({
Expand Down Expand Up @@ -341,6 +345,7 @@ const SendLinkSummary = ({ screenProps, styles }: AmountProps) => {
async (amount: string) => {
logger.debug('sendViaBridge', { amount, network })
try {
posthog.capture('used_bridge')
await new Promise((res, rej) => {
bridgePromiseRef.current = { res, rej }
sendBridgeRequest(amount, network.toLowerCase())
Expand All @@ -350,7 +355,7 @@ const SendLinkSummary = ({ screenProps, styles }: AmountProps) => {
showErrorDialog(t`Could not complete bridge transaction. Please try again.`, '', { onDismiss: goToRoot })
}
},
[amount],
[amount, posthog],
)

useEffect(() => {
Expand Down
1 change: 1 addition & 0 deletions src/components/faceVerification/standalone/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ const App = () => {
apiKey={Config.posthogApiKey}
options={{ host: Config.posthogHost, sendFeatureFlagEvent: false }}
autocapture={false}
person_profiles="identified_only"
sirpy marked this conversation as resolved.
Show resolved Hide resolved
>
<GlobalTogglesContextProvider>
<DialogContextProvider>
Expand Down
12 changes: 10 additions & 2 deletions src/components/invite/Invite.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Platform, TextInput, View } from 'react-native'
import { get, isNaN, isNil, noop } from 'lodash'
import { t } from '@lingui/macro'

import { usePostHog } from 'posthog-react-native'
import { CustomButton, Icon, Section, ShareButton, Text, Wrapper } from '../common'
import Avatar from '../common/view/Avatar'
import { WavesBox } from '../common/view/WavesBox'
Expand Down Expand Up @@ -442,8 +443,9 @@ const Invite = ({ screenProps, styles }) => {
const [invitees, refresh, level, inviteState] = useInvited()
const userStorage = useUserStorage()
const propSuffix = usePropSuffix()
const posthog = usePostHog()

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 +460,13 @@ const Invite = ({ screenProps, styles }) => {
}
}, [inviteState, propSuffix])

if (isNil(bounty) || isNaN(bounty)) {
useEffect(() => {
if (!isNaN(totalEarned) && totalEarned > 0) {
posthog.capture('has_invited')
}
}, [totalEarned, posthog])

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

Expand Down
67 changes: 67 additions & 0 deletions src/lib/API/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { find, isArray, shuffle } from 'lodash'
import type { $AxiosXHR, AxiosInstance, AxiosPromise } from 'axios'
import { padLeft } from 'web3-utils'

import moment from 'moment'
import { ethers } from 'ethers'
import Config, { fuseNetwork } from '../../config/config'

import { JWT } from '../constants/localStorage'
Expand Down Expand Up @@ -680,6 +682,71 @@ export class APIService {

return txs
}

async getAltClaim(account, chainId, lastBlock) {
sirpy marked this conversation as resolved.
Show resolved Hide resolved
if (!lastBlock) {
return
}
const explorer = Config.ethereum[chainId]?.explorerAPI
const sender32 = ethers.utils.hexZeroPad(account, 32)
const claimHash = '0x89ed24731df6b066e4c5186901fffdba18cd9a10f07494aff900bdee260d1304'

const now = moment().utc()
let today12UTC = moment().utc().set({ hour: 12, minute: 0, second: 0, millisecond: 0 })

if (now.isBefore(today12UTC)) {
today12UTC = today12UTC.subtract(1, 'day')
}

const timestamp12UTC = today12UTC.unix()

for (;;) {
const apis = shuffle(explorer.split(',')).map(baseURL => async () => {
const paramsBlockNo = {
module: 'block',
action: 'getblocknobytime',
timestamp: timestamp12UTC,
closest: 'before',
}

const blockNoOptions = { baseURL, params: paramsBlockNo }
const { result: blockNoStart } = await this.sharedClient.get('/api', blockNoOptions)

// return []
if (!blockNoStart) {
return false
}

const params = {
module: 'logs',
action: 'getLogs',
page: '1',
offset: '31',
Copy link
Contributor

Choose a reason for hiding this comment

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

why 31?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I actually don't know. I assume I thought for 31 days, but even that does not make any sense in this context.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

changed to 10k which should be default as well

topic0: claimHash,
topic1: sender32,

// below are required for fuse explorer, optional for <chain>scan.io
// blocknumber = fuse/blockscout, else we expect a <chain>scan.io aligned response
fromBlock: blockNoStart.blockNumber ?? blockNoStart,
toBlock: lastBlock,
topic0_1_opr: 'and',
}
const options = { baseURL, params }

const { result } = await this.sharedClient.get('/api', options)
if (!isArray(result)) {
log.warn('Failed to verify today`s claim transactions', { result, params, chainId, baseURL })
throw new Error('Failed to verify today`s claim transactions')
}
return result
})

// eslint-disable-next-line no-await-in-loop
const result = await fallback(apis)

return isArray(result) && result?.length > 0
}
}
}

const api = new APIService()
Expand Down
26 changes: 24 additions & 2 deletions src/lib/wallet/GoodWalletProvider.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Mainnet } from '@usedapp/core'
import { View } from 'react-native'
import { RadioButton } from 'react-native-paper'
import { t } from '@lingui/macro'
import { usePostHog } from 'posthog-react-native'

import Config from '../../config/config'
import logger from '../logger/js-logger'
Expand Down Expand Up @@ -63,6 +64,7 @@ export const GoodWalletContext = React.createContext({
login: undefined,
isLoggedInJWT: undefined,
dailyUBI: undefined,
dailyAltUBI: undefined,
isCitizen: false,
switchNetwork: undefined,
web3Provider: undefined,
Expand All @@ -86,8 +88,10 @@ export const GoodWalletProvider = ({ children, disableLoginAndWatch = false }) =
const [isLoggedInJWT, setLoggedInJWT] = useState()
const [balance, setBalance] = useState({ totalBalance: '0', balance: '0', fuseBalance: '0', celoBalance: '0' })
const [dailyUBI, setDailyUBI] = useState('0')
const [captureClaim, setCaptureClaim] = useState(undefined)
const [isCitizen, setIsCitizen] = useState()
const [shouldLoginAndWatch] = usePropsRefs([disableLoginAndWatch === false])
const posthog = usePostHog()

const db = getDB()

Expand All @@ -102,6 +106,10 @@ export const GoodWalletProvider = ({ children, disableLoginAndWatch = false }) =
})
}

const altWallet = networkId === 122 ? celowallet : fusewallet
const altLastBlock = await altWallet.getBlockNumber()
let hasClaimedAlt = false

if (supportsG$UBI(networkId)) {
if (UBIContract) {
calls.push({
Expand Down Expand Up @@ -145,12 +153,20 @@ export const GoodWalletProvider = ({ children, disableLoginAndWatch = false }) =
celoBalance: celoBalance.toFixed(2),
}

if (altLastBlock) {
hasClaimedAlt = await api.getAltClaim(account, altWallet.networkId, altLastBlock)
if (hasClaimedAlt && ubi === '0' && !captureClaim) {
posthog.capture('claimed_both')
setCaptureClaim(true)
}
}

log.debug('updateWalletData', { walletData })
setBalance(walletData)
setIsCitizen(isCitizen)
setDailyUBI(ubi)
},
[setBalance, setDailyUBI, setIsCitizen, fusewallet, celowallet],
[setBalance, setDailyUBI, setIsCitizen, fusewallet, celowallet, posthog, captureClaim],
)

const updateWalletListeners = useCallback(
Expand Down Expand Up @@ -204,6 +220,11 @@ export const GoodWalletProvider = ({ children, disableLoginAndWatch = false }) =

await wallet.ready

posthog?.register({
sirpy marked this conversation as resolved.
Show resolved Hide resolved
account: wallet.account,
$process_person_profile: false,
})

let web3Provider = seedOrWeb3

// create a web3provider compatible wallet, so can be compatible with @gooddollar/web3sdk-v2 and @gooddollar/good-design
Expand Down Expand Up @@ -258,7 +279,7 @@ export const GoodWalletProvider = ({ children, disableLoginAndWatch = false }) =
throw e
}
},
[setWalletAndStorage, isLoggedInRouter],
[setWalletAndStorage, isLoggedInRouter, posthog],
)

// react to initial set of wallet in initWalletAndStorage
Expand Down Expand Up @@ -482,6 +503,7 @@ export const useWalletData = () => {

return {
dailyUBI,
dailyAltUBI: 0,
balance,
totalBalance,
celoBalance,
Expand Down
Loading
Loading