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

4127: send to address #4137

Merged
merged 12 commits into from
Nov 21, 2023
2 changes: 2 additions & 0 deletions src/components/appNavigation/stackNavigation.js
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,7 @@ export const NextButton = ({
label,
canContinue,
loading,
...props
}: NextButtonProps) => {
const [next, ...nextRoutes] = nextRoutesParam ? nextRoutesParam : []
return (
Expand All @@ -476,6 +477,7 @@ export const NextButton = ({
style={Platform.OS === 'web' && { flex: 2 }}
canContinue={canContinue}
loading={loading}
{...props}
>
{label || 'Next'}
</PushButton>
Expand Down
3 changes: 2 additions & 1 deletion src/components/common/buttons/BaseButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Button = ({
children,
uppercase = true,
accessibilityLabel,
noElevation = false,
onPress = noop,
style,
theme,
Expand Down Expand Up @@ -126,7 +127,7 @@ const Button = ({
borderBottomRightRadius: style ? StyleSheet.flatten(style).borderBottomRightRadius ?? undefined : undefined,
}
const textStyle = { color: textColor, ...font }
const elevation = disabled || mode !== 'contained' ? 0 : elevationAnim
const elevation = disabled || mode !== 'contained' || noElevation ? 0 : elevationAnim

const isChildrenString = typeof children === 'string'
const fullSizeStyle = { flex: 1, alignSelf: 'stretch' }
Expand Down
10 changes: 8 additions & 2 deletions src/components/common/form/InputWithAdornment.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const InputText = ({
adornmentStyle,
adornmentColor,
adornmentDisabled = false,
iconAlignment = 'right',
Copy link
Collaborator

Choose a reason for hiding this comment

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

1, what have you decided about both paste + scan QR options ?
in this case you need to display 2 icon buttons somehow - need to discuss this

  1. you have repeated code. optimise it at leas by using useMemo:
const adornmentJsx = useMemo(() => showAdornment ? (
  <TouchableOpacity style={[styles.adornment, adornmentStyle]} disabled={adornmentDisabled} onPress={_onPress}>
   <Icon size={normalize(adornmentSize)} color={adornmentColor || inputColor} name={adornment} />
 </TouchableOpacity>
) : null, [styles, adornmentStyle, adornmentDisabled, _onPress, adornmentSize, adornmentColor, inputColor, adornment])

showError = true,
error,
styles,
Expand Down Expand Up @@ -76,15 +77,20 @@ const InputText = ({
return (
<View style={[styles.view, containerStyle]}>
<View style={styles.view}>
{showAdornment && error !== '' && iconAlignment === 'left' && (
<TouchableOpacity style={[styles.adornment, adornmentStyle]} disabled={adornmentDisabled} onPress={_onPress}>
<Icon size={normalize(adornmentSize)} color={adornmentColor || inputColor} name={adornment} />
</TouchableOpacity>
)}
<TextInput
{...props}
ref={getRef}
style={[styles.input, { borderBottomColor: inputColor, color: inputColor }, style]}
style={[styles.input, { borderBottomColor: error ? 'red' : inputColor, color: inputColor }, style]}
placeholderTextColor={placeholderTextColor || theme.colors.gray50Percent}
onTouchStart={onTouchStart}
onBlur={onBlurHandler}
/>
{showAdornment && error !== '' && (
{showAdornment && error !== '' && iconAlignment === 'right' && (
<TouchableOpacity style={[styles.adornment, adornmentStyle]} disabled={adornmentDisabled} onPress={_onPress}>
<Icon size={normalize(adornmentSize)} color={adornmentColor || inputColor} name={adornment} />
</TouchableOpacity>
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
{showAdornment && error !== '' && iconAlignment === 'right' && (
<TouchableOpacity style={[styles.adornment, adornmentStyle]} disabled={adornmentDisabled} onPress={_onPress}>
<Icon size={normalize(adornmentSize)} color={adornmentColor || inputColor} name={adornment} />
</TouchableOpacity>
{error !== '' && iconAlignment === 'right' && adornmentJsx}

Expand Down
213 changes: 182 additions & 31 deletions src/components/dashboard/Amount.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
// @flow
import React, { useCallback, useContext, useMemo, useState } from 'react'
import { KeyboardAvoidingView } from 'react-native'
import { KeyboardAvoidingView, View } from 'react-native'
import { t } from '@lingui/macro'
import { useGetBridgeData } from '@gooddollar/web3sdk-v2'

import InputWithAdornment from '../common/form/InputWithAdornment'
import logger from '../../lib/logger/js-logger'
import { AmountInput, ScanQRButton, Section, Wrapper } from '../common'
import { AmountInput, CustomButton, ScanQRButton, Section, Wrapper } from '../common'
import TopBar from '../common/view/TopBar'
import { BackButton, NextButton, useScreenState } from '../appNavigation/stackNavigation'
import {
Expand All @@ -14,11 +16,24 @@
useSwitchNetwork,
useWallet,
} from '../../lib/wallet/GoodWalletProvider'

// hooks
import usePermissions from '../permissions/hooks/usePermissions'
import { Permissions } from '../permissions/types'
import { useClipboardPaste } from '../../lib/hooks/useClipboard'

import { isIOS } from '../../lib/utils/platform'
import { withStyles } from '../../lib/styles'
import { getDesignRelativeWidth } from '../../lib/utils/sizes'
import Config from '../../config/config'
import { ACTION_RECEIVE, navigationOptions } from './utils/sendReceiveFlow'
import { theme } from '../theme/styles'
import {
ACTION_BRIDGE,
ACTION_RECEIVE,
ACTION_SEND,
ACTION_SEND_TO_ADDRESS,
navigationOptions,
} from './utils/sendReceiveFlow'

export type AmountProps = {
screenProps: any,
Expand All @@ -45,12 +60,81 @@

const log = logger.child({ from: 'Amount' })

const NextPageButton = ({ action, cbContinue, loading, values, ...props }) => {
const routeMap = {
[ACTION_BRIDGE]: ['SendLinkSummary', 'Home'],
[ACTION_RECEIVE]: ['Reason', 'ReceiveSummary', 'TransactionConfirmation'],
isNativeFlow: ['SendToAddress', 'SendLinkSummary'],
}

const nextRoute = routeMap[action] || ['Reason', 'SendLinkSummary', 'TransactionConfirmation']

return (
<NextButton
contentStyle={{ justifyContent: 'flex-start' }}
nextRoutes={nextRoute}
canContinue={cbContinue}
disabled={loading}
values={values}
action={action}
{...props}
/>
)
}

export const AddressDetails = ({ address, cb, error, setAddress, screenProps }) => {
const pasteUri = useClipboardPaste(data => {
setAddress(data)
})

// check clipboard permission an show dialog is not allowed
const [, requestClipboardPermissions] = usePermissions(Permissions.Clipboard, {
requestOnMounted: false,
onAllowed: pasteUri,
navigate: screenProps.navigate,
})
const handlePastePress = useCallback(requestClipboardPermissions)

return (
<View style={{ marginTop: 8 }}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 2, justifyContent: 'flex-end' }}>
<View
style={{
borderWidth: 1,
borderRadius: 5,
paddingLeft: 8,
paddingRight: 8,
paddingTop: 12,
paddingBottom: 12,
borderColor: error ? 'red' : theme.colors.primary,
}}
>
<InputWithAdornment
showAdornment={true}
adornment={'paste'}
adornmentSize={32}
adornmentAction={handlePastePress}
adornmentStyle={{ bottom: 0, left: 8, width: 16 }}
iconAlignment="left"
onChangeText={cb}
value={address}
error={error}
placeholder="Enter Wallet Address: 0x1234..."
style={{ borderBottomWidth: error ? 1 : 0, textAlign: 'left' }}
/>
</View>
</View>
</View>
</View>
)
}

const Amount = (props: AmountProps) => {
const { screenProps, styles } = props
const { push } = screenProps
const [screenState, setScreenState] = useScreenState(screenProps)
const { params = {} } = props.navigation.state
const { isBridge = false } = params
const { amount = 0, ...restState } = screenState || {}
const goodWallet = useWallet()
const { currentNetwork } = useSwitchNetwork()
Expand All @@ -59,7 +143,11 @@
const { native, token, balance } = useContext(TokenContext)
const { toDecimals, fromDecimals } = useFormatToken(token)
const formatFixed = useFixedDecimals(token)

const isNativeFlow = isDeltaApp && native
const isReceive = params && params.action === ACTION_RECEIVE
const isSend = params && params.action === ACTION_SEND
const isBridge = params && params.action === ACTION_BRIDGE

const bridgeState = isBridge
? {
Expand All @@ -71,10 +159,12 @@
const [GDAmount, setGDAmount] = useState(() => (amount ? formatFixed(amount) : ''))
const [loading, setLoading] = useState(() => !amount)
const [error, setError] = useState()
const [addressError, setAddressError] = useState()

const GDAmountInWei = useMemo(() => GDAmount && fromDecimals(GDAmount), [GDAmount])
const [sendViaAddress, setSendAddress] = useState(false)
const [address, setAddress] = useState('')

const isReceive = params && params.action === ACTION_RECEIVE
const GDAmountInWei = useMemo(() => GDAmount && fromDecimals(GDAmount), [GDAmount])

const handlePressQR = useCallback(() => push('SendByQR'), [push])

Expand All @@ -96,10 +186,14 @@
}
}

const canSend = await (isNativeFlow ? goodWallet.canSendNative(weiAmount) : goodWallet.canSend(weiAmount))

let canSend = await (isNativeFlow ? goodWallet.canSendNative(weiAmount) : goodWallet.canSend(weiAmount))
if (!canSend) {
setError(t`Sorry, you don't have enough ${token}s`)
return canSend
}

if (sendViaAddress) {
canSend = handleSendViaAddress(address)
}

return canSend
Expand All @@ -110,6 +204,20 @@
}
}

const handleSendViaAddress = input => {
setAddressError('')
setAddress(input)
const isEth = /^0x[a-fA-F0-9]{40}$/
const isEthAddress = isEth.test(input)
if (!isEthAddress) {
setAddressError(t`Sorry, this is not a valid address.`)
return false
}

setScreenState({ action: ACTION_SEND_TO_ADDRESS })
return true
}

const handleContinue = async () => {
setLoading(true)

Expand All @@ -127,6 +235,10 @@
setError('')
}

const handleRequestAddress = () => {
setSendAddress(value => !value)
}

const showScanQR = !isReceive && !params?.counterPartyDisplayName // ot in receive flow and also QR wasnt displayed on Who screen

return (
Expand All @@ -146,30 +258,69 @@
unit={token}
/>
</Section.Stack>
<Section.Row>
<Section.Row grow={1} justifyContent="flex-start">
<BackButton mode="text" screenProps={screenProps}>
{t`Cancel`}
</BackButton>
</Section.Row>
<Section.Stack grow={3} style={styles.nextButtonContainer}>
<NextButton
nextRoutes={
isBridge
? ['SendLinkSummary', 'Home']
: isReceive
? ['Reason', 'ReceiveSummary', 'TransactionConfirmation']
: isNativeFlow
? ['SendToAddress', 'SendLinkSummary']
: ['Reason', 'SendLinkSummary', 'TransactionConfirmation']
}
canContinue={handleContinue}
values={{ ...params, ...restState, amount: GDAmountInWei, ...bridgeState }}
disabled={loading}
{...props}
/>
{isSend && (
Copy link
Collaborator

Choose a reason for hiding this comment

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

have you tested it with the native flow ? set REACT_APP_DELTA=true in Your local env, at dev instance you will be able to switch onto Goerli and select gEth to send. If you need some Goerli pls ask me

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

yes I did test, have not tested sending any funds yet tho. will do

<Section.Stack style={{ marginBottom: 16 }}>
<CustomButton
icon={sendViaAddress ? 'success' : undefined}
iconAlignment="left"
iconColor={theme.colors.primary}
contentStyle={{ justifyContent: 'flex-start' }}
style={{ marginBottom: 8 }}
color={sendViaAddress ? theme.colors.white : theme.colors.primary}
textStyle={{ fontSize: 16, color: sendViaAddress ? theme.colors.primary : theme.colors.white }}
onPress={handleRequestAddress}
mode={'contained'}
withoutDone
noElevation
>
SEND VIA ADDRESS
</CustomButton>
{!sendViaAddress ? (
<NextPageButton
action={'Send'}
label="SEND VIA LINK"
cbContinue={handleContinue}
loading={loading}
values={{ ...params, ...restState, amount: GDAmountInWei, ...bridgeState }}
{...props}
/>
) : (
<AddressDetails
address={address}
cb={handleSendViaAddress}
setAddress={setAddress}
screenProps={screenProps}
error={addressError}
/>
)}
</Section.Stack>
</Section.Row>
)}

{!isSend ||
(isSend && sendViaAddress && (
<Section.Row>
<Section.Row grow={1} justifyContent="flex-start">
<BackButton mode="text" screenProps={screenProps}>
{t`Cancel`}
</BackButton>
</Section.Row>
<Section.Stack grow={3} style={styles.nextButtonContainer}>
<NextPageButton
action={isNativeFlow ? 'isNative' : sendViaAddress ? ACTION_SEND_TO_ADDRESS : params.action}
Fixed Show fixed Hide fixed
cbContinue={handleContinue}
loading={loading}
values={{
amount: GDAmountInWei,
address: address,
...params,
...restState,
...bridgeState,
}}
{...props}
/>
</Section.Stack>
</Section.Row>
))}
</Section>
</Wrapper>
</KeyboardAvoidingView>
Expand Down
2 changes: 1 addition & 1 deletion src/components/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ const Dashboard = props => {
const goToProfile = useOnPress(() => screenProps.push('Profile'), [screenProps])

const goToBridge = useCallback(() => {
screenProps.push('Amount', { isBridge: true })
screenProps.push('Amount', { action: 'Bridge' })
}, [screenProps])

const dispatchScrollEvent = useDebouncedCallback(() => fireEvent(SCROLL_FEED), 250)
Expand Down
4 changes: 2 additions & 2 deletions src/components/dashboard/SendLinkSummary.js
Original file line number Diff line number Diff line change
Expand Up @@ -212,8 +212,8 @@ const SendLinkSummary = ({ screenProps, styles }: AmountProps) => {
reason,
category,
amount,
senderEmail: vendorFields.email,
senderName: vendorFields.name,
senderEmail: vendorFields?.email,
senderName: vendorFields?.name,
invoiceId: vendorInfo?.invoiceId,
sellerWebsite: vendorInfo?.website,
sellerName: vendorInfo?.vendorName,
Expand Down
1 change: 1 addition & 0 deletions src/components/dashboard/utils/sendReceiveFlow.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const navigationOptions = ({ navigation }) => {
export const RECEIVE_TITLE = t`Receive`
export const SEND_TITLE = t`Send`
export const BRIDGE_TITLE = t`Bridge`
export const ACTION_BRIDGE = 'Bridge'
export const ACTION_RECEIVE = 'Receive'
export const ACTION_SEND = 'Send'
export const ACTION_SEND_TO_ADDRESS = 'SendToAddress'
Expand Down
Loading