Skip to content

Commit

Permalink
Merge pull request #100 from polymorpher/redirect
Browse files Browse the repository at this point in the history
Simple Redirect
  • Loading branch information
polymorpher authored Aug 30, 2021
2 parents cf76254 + e4f0462 commit 1d47304
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 13 deletions.
2 changes: 1 addition & 1 deletion code/client/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "one-wallet-web",
"version": "0.9.1",
"version": "0.9.2",
"private": true,
"license": "Apache-2.0",
"main": "client.js",
Expand Down
4 changes: 2 additions & 2 deletions code/client/src/components/AddressInput.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ const delayDomainOperationMillis = 1000
* Renders address input that provides type ahead search for any known addresses.
* Known addresses are addresses that have been entered by user for at least once.
*/
const AddressInput = ({ setAddressCallback, currentWallet, addressValue, extraSelectOptions }) => {
const AddressInput = ({ setAddressCallback, currentWallet, addressValue, extraSelectOptions, disableManualInput }) => {
const dispatch = useDispatch()

const [searchingAddress, setSearchingAddress] = useState(false)
Expand Down Expand Up @@ -176,7 +176,7 @@ const AddressInput = ({ setAddressCallback, currentWallet, addressValue, extraSe
}
}, [knownAddresses, setAddressCallback])

const showSelectManualInputAddress = util.safeOneAddress(addressValue.value) &&
const showSelectManualInputAddress = !disableManualInput && util.safeOneAddress(addressValue.value) &&
!wallets[util.safeNormalizedAddress(addressValue.value)] &&
!Object.keys(knownAddresses).includes(util.safeNormalizedAddress(addressValue.value))

Expand Down
8 changes: 8 additions & 0 deletions code/client/src/components/Text.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,11 @@ export const ExplorerLink = styled(Link).attrs(e => ({ ...e, style: { color: '#8
opacity: ${props => props['data-show-on-hover'] ? 1.0 : 0.8};
}
`

export const Ul = styled.ul`
list-style: none!important;
margin-left: 0;
padding-left: 1em;
text-indent: -1em;
`
export const Li = styled.li``
16 changes: 14 additions & 2 deletions code/client/src/constants/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,19 @@ export default {
dev: base + '/dev',
create: base + '/create',
wallets: base + '/wallets',
show: base + '/show/:address/:action?',
restore: base + '/restore',
showAddress: (address, action) => base + `/show/${address}${action ? `/${action}` : ''}`

show: base + '/show/:address/:action?',
showAddress: (address, action) => base + `/show/${address}${action ? `/${action}` : ''}`,

auth: base + '/auth/:action?/:address?',
doRedirect: (action, address) => {
if (!action) {
return base + '/redirect'
}
if (!address) {
return base + `/redirect/${action}`
}
return base + `/redirect/${action}/${address}`
},
}
109 changes: 109 additions & 0 deletions code/client/src/integration/Connect.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { Button, message, Row, Space, Typography, Select, Col, Tooltip } from 'antd'
import AnimatedSection from '../components/AnimatedSection'
import { AverageRow } from '../components/Grid'
import { Li, Ul } from '../components/Text'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { SearchOutlined } from '@ant-design/icons'
import util from '../util'
const { Title, Text, Paragraph } = Typography
const ConnectWallet = ({ caller, callback }) => {
const network = useSelector(state => state.wallet.network)
const wallets = useSelector(state => state.wallet.wallets)
const walletList = Object.keys(wallets).map(e => wallets[e]).filter(e => e.network === network)
const [selectedAddress, setSelectedAddress] = useState(walletList.length === 0
? {}
: { value: walletList[0].address, label: `(${walletList[0].name}) ${util.ellipsisAddress(util.safeOneAddress(walletList[0].address))}` })
const connect = () => {
if (!selectedAddress.value) {
return message.error('No address is selected')
}
if (!callback) {
message.error('The app did not specify a callback URL. Please contact the app developer.')
return
}
window.location.href = callback + `?address=${selectedAddress.value}&success=1`
}
const cancel = () => {
if (!callback) {
message.error('The app did not specify a callback URL. Please contact the app developer.')
return
}
window.location.href = callback + '?success=0'
}
return (
<AnimatedSection
show
style={{ minHeight: 320, maxWidth: 720 }}
>
<AverageRow>
<Space direction='vertical'>
<Title level={3}>"{caller}" wants to connect to your 1wallet</Title>
<Text>
<Paragraph>The app will be able to:</Paragraph>
<Ul>
<Li><span role='img' aria-label='-'></span> View the address of the connected wallet </Li>
</Ul>
<Paragraph>The app cannot:</Paragraph>
<Ul>
<Li><span role='img' aria-label='-'></span> Do anything without your permission (e.g. transferring funds, sign transactions, ...)</Li>
</Ul>
</Text>
</Space>
</AverageRow>

<AverageRow>
<Text>Select a wallet you want to connect:</Text>
</AverageRow>
<AverageRow>
<Select
suffixIcon={<SearchOutlined />}
placeholder='one1......'
labelInValue
bordered={false}
showSearch
style={{
width: '100%',
borderBottom: '1px dashed black'
}}
value={selectedAddress}
onBlur={() => {}}
onSearch={() => {}}
>
{walletList.map(wallet => {
const { address, name } = wallet
const oneAddress = util.safeOneAddress(address)
const displayText = `(${name}) ${util.ellipsisAddress(oneAddress)}`
return (
<Select.Option key={displayText} value={displayText} style={{ padding: 0 }}>
<Row align='left'>
<Col span={24}>
<Tooltip title={oneAddress}>
<Button
block
type='text'
style={{ textAlign: 'left', height: '50px' }}
onClick={() => {
setSelectedAddress({ value: address, label: displayText })
}}
>
{displayText}
</Button>
</Tooltip>
</Col>
</Row>
</Select.Option>
)
})}
</Select>
</AverageRow>
<AverageRow justify='space-between'>
<Button size='large' type='text' onClick={cancel} danger>Cancel</Button>
<Button type='primary' size='large' shape='round' onClick={connect} disabled={!selectedAddress.value}>Connect</Button>
</AverageRow>

</AnimatedSection>
)
}

export default ConnectWallet
155 changes: 155 additions & 0 deletions code/client/src/integration/RequestPayment.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { Button, message, Row, Space, Typography, Select, Col, Tooltip } from 'antd'
import BN from 'bn.js'
import AnimatedSection from '../components/AnimatedSection'
import { AverageRow } from '../components/Grid'
import { Hint } from '../components/Text'
import React, { useState } from 'react'
import { useSelector } from 'react-redux'
import { SearchOutlined } from '@ant-design/icons'
import util from '../util'
import WalletAddress from '../components/WalletAddress'
import Send from '../pages/Show/Send'
import { handleAddressError } from '../handler'
import { HarmonyONE } from '../components/TokenAssets'
const { Title, Text, Paragraph } = Typography
const RequestPaymennt = ({ caller, callback, amount, dest, from }) => {
dest = util.safeNormalizedAddress(dest)
const network = useSelector(state => state.wallet.network)
const balances = useSelector(state => state.wallet.balances)
const price = useSelector(state => state.wallet.price)
const wallets = useSelector(state => state.wallet.wallets)
const walletList = Object.keys(wallets).map(e => wallets[e]).filter(e => e.network === network)
const selectedWallet = from && wallets[from]
const defaultUserAddress = (walletList.length === 0 ? {} : { value: walletList[0].address, label: `(${walletList[0].name}) ${util.ellipsisAddress(util.safeOneAddress(walletList[0].address))}` })
const [selectedAddress, setSelectedAddress] = useState(from ? (selectedWallet || {}) : defaultUserAddress)
const { formatted: amountFormatted, fiatFormatted: amountFiatFormatted } = util.computeBalance(amount, price)
const [showSend, setShowSend] = useState(false)
const checkCallback = () => {
if (!callback) {
message.error('The app did not specify a callback URL. Please contact the app developer.')
return false
}
return true
}
const next = () => {
if (!selectedAddress.value) {
return message.error('No address is selected')
}
const normalizedAddress = util.safeExec(util.normalizedAddress, [selectedAddress.value], handleAddressError)
if (!normalizedAddress) {
return message.error(`normalizedAddress=${normalizedAddress}`)
}
const balance = balances[selectedAddress.value]
if (!(new BN(amount).lte(new BN(balance)))) {
const { formatted: balanceFormatted } = util.computeBalance(balance)
return message.error(`Insufficient balance (${balanceFormatted} ONE) in the selected wallet`)
}
if (!checkCallback()) return
setShowSend(true)
}
const cancel = () => {
if (!checkCallback()) return
window.location.href = callback + '?success=0'
}
const onSendClose = () => {
setShowSend(false)
}
const onSuccess = (txId) => {
if (!checkCallback()) return
window.location.href = callback + `?success=1&txId=${txId}`
}

return (
<>
<AnimatedSection
show
style={{ minHeight: 320, maxWidth: 720 }}
>
<AverageRow>
<Space direction='vertical'>
<Title level={3}>"{caller}" wants you to pay </Title>
<Title level={3}>{amountFormatted} ONE <Hint>(≈ ${amountFiatFormatted} USD)</Hint></Title>
<Paragraph>To: <WalletAddress showLabel address={dest} /></Paragraph>
</Space>
</AverageRow>
{from && !selectedWallet &&
<AverageRow>
<Space direction='vertical'>
<Paragraph>The app wants you to pay from address:</Paragraph>
<Paragraph> <WalletAddress showLabel address={from} /></Paragraph>
<Paragraph>However, you do not have that 1wallet address. Please go back to the app, and choose an 1wallet address that you own. If you do own that 1wallet address but it is not appearing in your wallets, you need restore the wallet first using "Restore" feature with your Google Authenticator.</Paragraph>
</Space>
</AverageRow>}
{from && selectedWallet &&
<AverageRow>
<Paragraph>Paying from: <WalletAddress showLabel address={from} /></Paragraph>
</AverageRow>}
{!from &&
<>
<AverageRow>
<Text>Select a wallet you want to use:</Text>
</AverageRow>
<AverageRow>
<Select
suffixIcon={<SearchOutlined />}
placeholder='one1......'
labelInValue
bordered={false}
showSearch
style={{
width: '100%',
borderBottom: '1px dashed black'
}}
value={selectedAddress}
onBlur={() => {}}
onSearch={() => {}}
>
{walletList.map(wallet => {
const { address, name } = wallet
const oneAddress = util.safeOneAddress(address)
const displayText = `(${name}) ${util.ellipsisAddress(oneAddress)}`
return (
<Select.Option key={displayText} value={displayText} style={{ padding: 0 }}>
<Row align='left'>
<Col span={24}>
<Tooltip title={oneAddress}>
<Button
block
type='text'
style={{ textAlign: 'left', height: '50px' }}
onClick={() => {
setSelectedAddress({ value: address, label: displayText })
}}
>
{displayText}
</Button>
</Tooltip>
</Col>
</Row>
</Select.Option>
)
})}
</Select>
</AverageRow>
</>}
{!showSend &&
<AverageRow justify='space-between'>
<Button size='large' type='text' onClick={cancel} danger>Cancel</Button>
<Button
type='primary' size='large' shape='round' onClick={next}
disabled={!(selectedAddress.value)}
>Next
</Button>
</AverageRow>}

</AnimatedSection>
{showSend &&
<Send
address={selectedAddress.value} show={showSend} onClose={onSendClose} onSuccess={onSuccess}
prefillAmount={amountFormatted} prefillDest={dest} overrideToken={HarmonyONE}
/>}
</>
)
}

export default RequestPaymennt
32 changes: 32 additions & 0 deletions code/client/src/integration/WalletAuth.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Redirect, useLocation, useRouteMatch } from 'react-router'
import Paths from '../constants/paths'
import util from '../util'
import React from 'react'
import querystring from 'query-string'
import ConnectWallet from './Connect'
import RequestPayment from './RequestPayment'

const WalletAuth = () => {
const location = useLocation()
const match = useRouteMatch(Paths.auth)
const { action, address: routeAddress } = match ? match.params : {}
const oneAddress = util.safeOneAddress(routeAddress)
const address = util.safeNormalizedAddress(routeAddress)

const qs = querystring.parse(location.search)
const callback = qs.callback && Buffer.from(qs.callback, 'base64').toString()
const caller = qs.caller
const { amount, dest, from } = qs

if (!action || !callback || !caller) {
return <Redirect to={Paths.wallets} />
}
return (
<>
{action === 'connect' && <ConnectWallet caller={caller} callback={callback} />}
{action === 'pay' && <RequestPayment caller={caller} callback={callback} amount={amount} dest={dest} from={from} />}
</>
)
}

export default WalletAuth
Loading

0 comments on commit 1d47304

Please sign in to comment.