Skip to content

Commit

Permalink
feat(jumpstart): add share with QR code (#5118)
Browse files Browse the repository at this point in the history
### Description

This PR adds:
1. share QR bottom sheet
2. analytics for the whole share screen
3. extra unit tests

### Test plan


https://github.com/valora-inc/wallet/assets/20150449/c5ff24ab-db85-47f6-9c57-a887bca54186




### Related issues

- Fixes RET-995

### Backwards compatibility

Y

### Network scalability

T
  • Loading branch information
kathaypacific authored Mar 19, 2024
1 parent de79d89 commit 409ace7
Show file tree
Hide file tree
Showing 8 changed files with 266 additions and 49 deletions.
7 changes: 6 additions & 1 deletion locales/base/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -2132,7 +2132,12 @@
"ctaNavigate": "Go Home"
},
"ctaShare": "Share Link",
"shareMessage": "Hi! I’m sending you a link funded with {{tokenAmount}} {{tokenSymbol}} on {{appName}}. Use this link to download the app and the funds will be live, waiting for you. {{link}}"
"ctaScanQRCode": "Scan QR Code",
"shareMessage": "Hi! I’m sending you a link funded with {{tokenAmount}} {{tokenSymbol}} on {{appName}}. Use this link to download the app and the funds will be live, waiting for you. {{link}}",
"qrCodeBottomSheet": {
"title": "Scan the code to receive funds",
"description": "Check the status of your funds on the homescreen. Alternatively, copy or share the link below."
}
},
"transactionFeed": {
"approvalTransactionTitle": "Approval",
Expand Down
7 changes: 7 additions & 0 deletions src/analytics/Events.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,13 @@ export enum JumpstartEvents {
jumpstart_send_succeeded = 'jumpstart_send_succeeded',
jumpstart_send_failed = 'jumpstart_send_failed',
jumpstart_send_cancelled = 'jumpstart_send_cancelled',
jumpstart_share_link = 'jumpstart_share_link',
jumpstart_share_link_result = 'jumpstart_share_link_result',
jumpstart_show_QR = 'jumpstart_show_QR',
jumpstart_copy_link = 'jumpstart_copy_link',
jumpstart_share_close = 'jumpstart_share_close',
jumpstart_share_confirm_close = 'jumpstart_share_confirm_close',
jumpstart_share_dismiss_close = 'jumpstart_share_dismiss_close',
jumpstart_claim_succeeded = 'jumpstart_claim_succeeded',
jumpstart_claim_failed = 'jumpstart_claim_failed',
jumpstart_claimed_token = 'jumpstart_claimed_token',
Expand Down
36 changes: 25 additions & 11 deletions src/analytics/Properties.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
FiatConnectError,
KycStatus as FiatConnectKycStatus,
} from '@fiatconnect/fiatconnect-types'
import { ShareAction } from 'react-native'
import { PermissionStatus } from 'react-native-permissions'
import {
AppEvents,
Expand Down Expand Up @@ -504,10 +505,7 @@ interface InviteEventsProperties {
phoneNumberHash: string | null
}
[InviteEvents.invite_with_share_dismiss]: undefined
[InviteEvents.invite_with_referral_url]: {
action: 'sharedAction' | 'dismissedAction'
activityType?: string | undefined
}
[InviteEvents.invite_with_referral_url]: ShareAction
[InviteEvents.opened_via_invite_url]: {
inviterAddress: string
}
Expand Down Expand Up @@ -1499,20 +1497,24 @@ interface TransactionDetailsProperties {
}
}

interface JumpstartSendProperties {
interface JumpstartDepositProperties {
amountInUsd: string | null
tokenId: string | null
networkId: string | null
}
interface JumpstartSendProperties extends JumpstartDepositProperties {
localCurrency: LocalCurrencyCode
localCurrencyExchangeRate: string | null
tokenSymbol: string
tokenAmount: string | null
amountInUsd: string | null
tokenId: string | null
networkId: string | null
}
export enum JumpstartShareOrigin {
QrScreen = 'qrScreen',
MainScreen = 'mainScreen',
}
interface JumpstartEventsProperties {
[JumpstartEvents.send_select_recipient_jumpstart]: undefined
[JumpstartEvents.jumpstart_send_amount_exceeds_threshold]: {
tokenId: string
sendAmountUsd: string
[JumpstartEvents.jumpstart_send_amount_exceeds_threshold]: JumpstartDepositProperties & {
thresholdUsd: number
}
[JumpstartEvents.jumpstart_send_amount_continue]: JumpstartSendProperties
Expand All @@ -1521,6 +1523,18 @@ interface JumpstartEventsProperties {
[JumpstartEvents.jumpstart_send_succeeded]: JumpstartSendProperties
[JumpstartEvents.jumpstart_send_failed]: JumpstartSendProperties
[JumpstartEvents.jumpstart_send_cancelled]: JumpstartSendProperties
[JumpstartEvents.jumpstart_share_link]: JumpstartDepositProperties & {
origin: JumpstartShareOrigin
}
[JumpstartEvents.jumpstart_share_link_result]: JumpstartDepositProperties &
(ShareAction | { error: string })
[JumpstartEvents.jumpstart_show_QR]: JumpstartDepositProperties
[JumpstartEvents.jumpstart_copy_link]: JumpstartDepositProperties & {
origin: JumpstartShareOrigin
}
[JumpstartEvents.jumpstart_share_close]: undefined
[JumpstartEvents.jumpstart_share_confirm_close]: undefined
[JumpstartEvents.jumpstart_share_dismiss_close]: undefined
[JumpstartEvents.jumpstart_claim_succeeded]: undefined
[JumpstartEvents.jumpstart_claim_failed]: undefined
[JumpstartEvents.jumpstart_claimed_token]: {
Expand Down
7 changes: 7 additions & 0 deletions src/analytics/docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,13 @@ export const eventDocs: Record<AnalyticsEventType, string> = {
[JumpstartEvents.jumpstart_send_cancelled]: `When the user cancels the transaction, e.g. by exiting the pincode enter screen`,
[JumpstartEvents.jumpstart_send_succeeded]: `When the transactions are successfully settled on the network`,
[JumpstartEvents.jumpstart_send_failed]: `When the transactions failed to send or are reverted by the network`,
[JumpstartEvents.jumpstart_share_link]: `When the user taps on the share link button on the jumpstart share screen or qr bottom sheet`,
[JumpstartEvents.jumpstart_share_link_result]: `The result of the user's share action, can be shared or dismissed or error`,
[JumpstartEvents.jumpstart_show_QR]: `When the user taps the CTA to launch the QR code bottom sheet on the share screen`,
[JumpstartEvents.jumpstart_copy_link]: `When the user copies the link from the share screen or QR code bottom sheet`,
[JumpstartEvents.jumpstart_share_close]: `When the user tries to navigate away from the jumpstart share screen`,
[JumpstartEvents.jumpstart_share_confirm_close]: `When the user confirms on the popup that they want to navigate away`,
[JumpstartEvents.jumpstart_share_dismiss_close]: `When the user dismisses the popup and does not navigate away from the jumpstart share screen`,
[JumpstartEvents.jumpstart_claim_succeeded]: `When claiming from Wallet Jumpstart succeeded`,
[JumpstartEvents.jumpstart_claim_failed]: `When claiming from Wallet Jumpstart failed`,
[JumpstartEvents.jumpstart_claimed_token]: `When user successfully claimed an ERC20 token trough Wallet Jumpstart`,
Expand Down
5 changes: 3 additions & 2 deletions src/icons/QRCode.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import * as React from 'react'
import Svg, { Path } from 'react-native-svg'
import Colors from 'src/styles/colors'

const QRCode = () => (
const QRCode = ({ color = Colors.black }: { color?: Colors }) => (
<Svg width={18} height={18}>
<Path
fill="#000"
fill={color}
d="M0 8h2v2H0V8Zm8-6h2v4H8V2ZM6 8h4v4H8v-2H6V8Zm6 0h2v2h2V8h2v2h-2v2h2v4h-2v2h-2v-2h-4v2H8v-4h4v-2h2v-2h-2V8Zm4 8v-4h-2v4h2ZM12 0h6v6h-6V0Zm2 2v2h2V2h-2ZM0 0h6v6H0V0Zm2 2v2h2V2H2ZM0 12h6v6H0v-6Zm2 2v2h2v-2H2Z"
/>
</Svg>
Expand Down
3 changes: 2 additions & 1 deletion src/jumpstart/JumpstartEnterAmount.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,9 +136,10 @@ function JumpstartEnterAmount() {
setSendAmountExceedsThreshold(sendAmountExceedsMax)
if (sendAmountExceedsMax) {
ValoraAnalytics.track(JumpstartEvents.jumpstart_send_amount_exceeds_threshold, {
sendAmountUsd: sendAmountUsd.toFixed(2),
amountInUsd: sendAmountUsd.toFixed(2),
tokenId: token.tokenId,
thresholdUsd: jumpstartSendThreshold,
networkId: token.networkId,
})
}

Expand Down
148 changes: 120 additions & 28 deletions src/jumpstart/JumpstartShareLink.test.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import Clipboard from '@react-native-clipboard/clipboard'
import { fireEvent, render, waitFor, within } from '@testing-library/react-native'
import React from 'react'
import { Share } from 'react-native'
import { Provider } from 'react-redux'
import { JumpstartEvents } from 'src/analytics/Events'
import ValoraAnalytics from 'src/analytics/ValoraAnalytics'
import JumpstartShareLink from 'src/jumpstart/JumpstartShareLink'
import { navigateHome } from 'src/navigator/NavigationService'
import MockedNavigator from 'test/MockedNavigator'
Expand All @@ -13,8 +16,8 @@ describe('JumpstartShareLink', () => {
jest.clearAllMocks()
})

it('should render the correct information', () => {
const { getByText, getByTestId, queryByText } = render(
function renderJumpstartShareLink() {
return render(
<Provider store={createMockStore()}>
<MockedNavigator
component={JumpstartShareLink}
Expand All @@ -26,58 +29,142 @@ describe('JumpstartShareLink', () => {
/>
</Provider>
)
}
const expectedTrackedProperties = {
tokenId: mockCusdTokenId,
networkId: 'celo-alfajores',
amountInUsd: '12.345',
}

it('should render the correct information', () => {
const { getByText, getByTestId, queryByText } = renderJumpstartShareLink()

expect(getByText('jumpstartShareLinkScreen.title')).toBeTruthy()
expect(getByText('jumpstartShareLinkScreen.description, {"tokenSymbol":"cUSD"}')).toBeTruthy()
expect(getByTestId('JumpstartShareLink/LiveLink')).toHaveTextContent('https://some.link')
expect(getByText('jumpstartShareLinkScreen.ctaShare')).toBeEnabled()
expect(
within(getByTestId('JumpstartShareLink/ScrollView')).getByText(
'jumpstartShareLinkScreen.ctaShare'
)
).toBeEnabled()
expect(queryByText('jumpstartShareLinkScreen.navigationWarning.title')).toBeFalsy()
})

it('should copy the link on press copy', async () => {
const { getByTestId } = renderJumpstartShareLink()

fireEvent.press(getByTestId('JumpstartShareLink/LiveLink/Copy'))

expect(ValoraAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.jumpstart_copy_link, {
...expectedTrackedProperties,
origin: 'mainScreen',
})
expect(Clipboard.setString).toHaveBeenCalledWith('https://some.link')
})

it('should call the native share on press share', async () => {
const mockShare = jest.spyOn(Share, 'share')
const { getByText } = render(
<Provider store={createMockStore()}>
<MockedNavigator
component={JumpstartShareLink}
params={{
tokenId: mockCusdTokenId,
sendAmount: '12.345',
link: 'https://some.link',
}}
/>
</Provider>
const { getByTestId } = renderJumpstartShareLink()

fireEvent.press(
within(getByTestId('JumpstartShareLink/ScrollView')).getByText(
'jumpstartShareLinkScreen.ctaShare'
)
)

await waitFor(() =>
expect(mockShare).toHaveBeenCalledWith({
message:
'jumpstartShareLinkScreen.shareMessage, {"link":"https://some.link","tokenAmount":"12.345","tokenSymbol":"cUSD"}',
})
)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.jumpstart_share_link, {
...expectedTrackedProperties,
origin: 'mainScreen',
})
})

it('should track the outcome of the share', async () => {
jest.spyOn(Share, 'share').mockResolvedValueOnce({
action: 'sharedAction',
})
jest.spyOn(Share, 'share').mockRejectedValueOnce(new Error('shareError'))
const { getByTestId } = renderJumpstartShareLink()

fireEvent.press(
within(getByTestId('JumpstartShareLink/ScrollView')).getByText(
'jumpstartShareLinkScreen.ctaShare'
)
)
await waitFor(() =>
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
JumpstartEvents.jumpstart_share_link_result,
{
...expectedTrackedProperties,
action: 'sharedAction',
}
)
)

fireEvent.press(
within(getByTestId('JumpstartShareLink/ScrollView')).getByText(
'jumpstartShareLinkScreen.ctaShare'
)
)
await waitFor(() =>
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
JumpstartEvents.jumpstart_share_link_result,
{
...expectedTrackedProperties,
error: 'shareError',
}
)
)
})

it('should open the QR share bottom sheet on press share QR', async () => {
const mockShare = jest.spyOn(Share, 'share')
const { getByText, getByTestId } = renderJumpstartShareLink()

fireEvent.press(getByText('jumpstartShareLinkScreen.ctaScanQRCode'))
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
JumpstartEvents.jumpstart_show_QR,
expectedTrackedProperties
)

fireEvent.press(getByTestId('JumpstartShareLink/QRCodeBottomSheet/LiveLink/Copy'))
expect(ValoraAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.jumpstart_copy_link, {
...expectedTrackedProperties,
origin: 'qrScreen',
})

fireEvent.press(getByText('jumpstartShareLinkScreen.ctaShare'))
fireEvent.press(
within(getByTestId('JumpstartShareLink/QRCodeBottomSheet')).getByText(
'jumpstartShareLinkScreen.ctaShare'
)
)

await waitFor(() =>
expect(mockShare).toHaveBeenCalledWith({
message:
'jumpstartShareLinkScreen.shareMessage, {"link":"https://some.link","tokenAmount":"12.345","tokenSymbol":"cUSD"}',
})
)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.jumpstart_share_link, {
...expectedTrackedProperties,
origin: 'qrScreen',
})
})

it('should warn the user before navigating away', async () => {
const { getByText, getByTestId, queryByText } = render(
<Provider store={createMockStore()}>
<MockedNavigator
component={JumpstartShareLink}
params={{
tokenId: mockCusdTokenId,
sendAmount: '12.345',
link: 'https://some.link',
}}
/>
</Provider>
)
const { getByText, getByTestId, queryByText } = renderJumpstartShareLink()

fireEvent.press(getByTestId('JumpstartShareLink/CloseButton'))
// warning should be shown
await waitFor(() =>
expect(getByText('jumpstartShareLinkScreen.navigationWarning.title')).toBeTruthy()
)
expect(ValoraAnalytics.track).toHaveBeenCalledWith(JumpstartEvents.jumpstart_share_close)

// should not navigate away if the user taps on the primary action
fireEvent.press(
Expand All @@ -89,11 +176,16 @@ describe('JumpstartShareLink', () => {
expect(queryByText('jumpstartShareLinkScreen.navigationWarning.title')).toBeFalsy()
)
expect(navigateHome).not.toHaveBeenCalled()
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
JumpstartEvents.jumpstart_share_dismiss_close
)

fireEvent.press(getByTestId('JumpstartShareLink/CloseButton'))
// should navigate away if the user taps on the secondary action
fireEvent.press(getByText('jumpstartShareLinkScreen.navigationWarning.ctaNavigate'))

await waitFor(() => expect(navigateHome).toHaveBeenCalled())
expect(ValoraAnalytics.track).toHaveBeenCalledWith(
JumpstartEvents.jumpstart_share_confirm_close
)
})
})
Loading

0 comments on commit 409ace7

Please sign in to comment.