Merge branch 'development' into 536-notifications-status-labels-sync
# Conflicts: # src/routes/safe/components/Balances/SendModal/screens/SendCollectible/index.jsx
This commit is contained in:
commit
5c95d1f220
|
@ -0,0 +1,51 @@
|
||||||
|
// @flow
|
||||||
|
import { makeStyles } from '@material-ui/core/styles'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import QRIcon from '~/assets/icons/qrcode.svg'
|
||||||
|
import ScanQRModal from '~/components/ScanQRModal'
|
||||||
|
import Img from '~/components/layout/Img'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
handleScan: Function,
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyles = makeStyles({
|
||||||
|
qrCodeBtn: {
|
||||||
|
cursor: 'pointer',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export const ScanQRWrapper = (props: Props) => {
|
||||||
|
const classes = useStyles()
|
||||||
|
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const openQrModal = () => {
|
||||||
|
setQrModalOpen(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeQrModal = () => {
|
||||||
|
setQrModalOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onScanFinished = (value) => {
|
||||||
|
props.handleScan(value, closeQrModal)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Img
|
||||||
|
alt="Scan QR"
|
||||||
|
className={classes.qrCodeBtn}
|
||||||
|
height={20}
|
||||||
|
onClick={() => {
|
||||||
|
openQrModal()
|
||||||
|
}}
|
||||||
|
role="button"
|
||||||
|
src={QRIcon}
|
||||||
|
/>
|
||||||
|
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={onScanFinished} />}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ export const saveAddressBook = async (addressBook: AddressBook) => {
|
||||||
export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
|
export const getAddressesListFromAdbk = (addressBook: AddressBook) =>
|
||||||
Array.from(addressBook).map((entry) => entry.address)
|
Array.from(addressBook).map((entry) => entry.address)
|
||||||
|
|
||||||
const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => {
|
export const getNameFromAdbk = (addressBook: AddressBook, userAddress: string): string | null => {
|
||||||
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
|
const entry = addressBook.find((addressBookItem) => addressBookItem.address === userAddress)
|
||||||
if (entry) {
|
if (entry) {
|
||||||
return entry.name
|
return entry.name
|
||||||
|
|
|
@ -4,12 +4,14 @@ import { withStyles } from '@material-ui/core/styles'
|
||||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
|
|
||||||
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import OpenPaper from '~/components/Stepper/OpenPaper'
|
import OpenPaper from '~/components/Stepper/OpenPaper'
|
||||||
import AddressInput from '~/components/forms/AddressInput'
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
import { mustBeEthereumAddress, noErrorsOn, required } from '~/components/forms/validator'
|
import { mustBeEthereumAddress, noErrorsOn, required } from '~/components/forms/validator'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
|
import { SAFE_MASTER_COPY_ADDRESS_V10, getSafeMasterContract, validateProxy } from '~/logic/contracts/safeContracts'
|
||||||
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
import { getWeb3 } from '~/logic/wallets/getWeb3'
|
||||||
|
@ -80,64 +82,77 @@ export const safeFieldsValidation = async (values: Object) => {
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
const Details = ({ classes, errors, form }: Props) => (
|
const Details = ({ classes, errors, form }: Props) => {
|
||||||
<>
|
const handleScan = (value, closeQrModal) => {
|
||||||
<Block margin="md">
|
form.mutators.setValue(FIELD_LOAD_ADDRESS, value)
|
||||||
<Paragraph color="primary" noMargin size="md">
|
closeQrModal()
|
||||||
You are about to load an existing Gnosis Safe. First, choose a name and enter the Safe address. The name is only
|
}
|
||||||
stored locally and will never be shared with Gnosis or any third parties.
|
return (
|
||||||
<br />
|
<>
|
||||||
Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you a
|
<Block margin="md">
|
||||||
read-only view.
|
<Paragraph color="primary" noMargin size="md">
|
||||||
</Paragraph>
|
You are about to load an existing Gnosis Safe. First, choose a name and enter the Safe address. The name is
|
||||||
</Block>
|
only stored locally and will never be shared with Gnosis or any third parties.
|
||||||
<Block className={classes.root}>
|
<br />
|
||||||
<Field
|
Your connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you
|
||||||
component={TextField}
|
a read-only view.
|
||||||
name={FIELD_LOAD_NAME}
|
</Paragraph>
|
||||||
placeholder="Name of the Safe"
|
</Block>
|
||||||
text="Safe name"
|
<Block className={classes.root}>
|
||||||
type="text"
|
<Col xs={11}>
|
||||||
validate={required}
|
<Field
|
||||||
/>
|
component={TextField}
|
||||||
</Block>
|
name={FIELD_LOAD_NAME}
|
||||||
<Block className={classes.root} margin="lg">
|
placeholder="Name of the Safe"
|
||||||
<AddressInput
|
text="Safe name"
|
||||||
component={TextField}
|
type="text"
|
||||||
fieldMutator={(val) => {
|
validate={required}
|
||||||
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
/>
|
||||||
}}
|
</Col>
|
||||||
inputAdornment={
|
</Block>
|
||||||
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
<Block className={classes.root} margin="lg">
|
||||||
endAdornment: (
|
<Col xs={11}>
|
||||||
<InputAdornment position="end">
|
<AddressInput
|
||||||
<CheckCircle className={classes.check} />
|
component={TextField}
|
||||||
</InputAdornment>
|
fieldMutator={(val) => {
|
||||||
),
|
form.mutators.setValue(FIELD_LOAD_ADDRESS, val)
|
||||||
}
|
}}
|
||||||
}
|
inputAdornment={
|
||||||
name={FIELD_LOAD_ADDRESS}
|
noErrorsOn(FIELD_LOAD_ADDRESS, errors) && {
|
||||||
placeholder="Safe Address*"
|
endAdornment: (
|
||||||
text="Safe Address"
|
<InputAdornment position="end">
|
||||||
type="text"
|
<CheckCircle className={classes.check} />
|
||||||
/>
|
</InputAdornment>
|
||||||
</Block>
|
),
|
||||||
<Block margin="sm">
|
}
|
||||||
<Paragraph className={classes.links} color="primary" noMargin size="md">
|
}
|
||||||
By continuing you consent with the{' '}
|
name={FIELD_LOAD_ADDRESS}
|
||||||
<a href="https://safe.gnosis.io/terms" rel="noopener noreferrer" target="_blank">
|
placeholder="Safe Address*"
|
||||||
terms of use
|
text="Safe Address"
|
||||||
</a>{' '}
|
type="text"
|
||||||
and{' '}
|
/>
|
||||||
<a href="https://safe.gnosis.io/privacy" rel="noopener noreferrer" target="_blank">
|
</Col>
|
||||||
privacy policy
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
</a>
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the
|
</Col>
|
||||||
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point.
|
</Block>
|
||||||
</Paragraph>
|
<Block margin="sm">
|
||||||
</Block>
|
<Paragraph className={classes.links} color="primary" noMargin size="md">
|
||||||
</>
|
By continuing you consent with the{' '}
|
||||||
)
|
<a href="https://safe.gnosis.io/terms" rel="noopener noreferrer" target="_blank">
|
||||||
|
terms of use
|
||||||
|
</a>{' '}
|
||||||
|
and{' '}
|
||||||
|
<a href="https://safe.gnosis.io/privacy" rel="noopener noreferrer" target="_blank">
|
||||||
|
privacy policy
|
||||||
|
</a>
|
||||||
|
. Most importantly, you confirm that your funds are held securely in the Gnosis Safe, a smart contract on the
|
||||||
|
Ethereum blockchain. These funds cannot be accessed by Gnosis at any point.
|
||||||
|
</Paragraph>
|
||||||
|
</Block>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const DetailsForm = withStyles(styles)(Details)
|
const DetailsForm = withStyles(styles)(Details)
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import { useSelector } from 'react-redux'
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
import Modal from '~/components/Modal'
|
import Modal from '~/components/Modal'
|
||||||
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import AddressInput from '~/components/forms/AddressInput'
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
|
@ -15,6 +16,7 @@ import TextField from '~/components/forms/TextField'
|
||||||
import { composeValidators, minMaxLength, required, uniqueAddress } from '~/components/forms/validator'
|
import { composeValidators, minMaxLength, required, uniqueAddress } from '~/components/forms/validator'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
|
import Col from '~/components/layout/Col'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
@ -81,34 +83,53 @@ const CreateEditEntryModalComponent = ({
|
||||||
<GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}>
|
<GnoForm formMutators={formMutators} onSubmit={onFormSubmitted}>
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
const mutators = args[3]
|
const mutators = args[3]
|
||||||
|
const handleScan = (value, closeQrModal) => {
|
||||||
|
let scannedAddress = value
|
||||||
|
|
||||||
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
mutators.setOwnerAddress(scannedAddress)
|
||||||
|
closeQrModal()
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.container}>
|
<Block className={classes.container}>
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<Field
|
<Col xs={11}>
|
||||||
className={classes.addressInput}
|
<Field
|
||||||
component={TextField}
|
className={classes.addressInput}
|
||||||
defaultValue={entryToEdit ? entryToEdit.entry.name : undefined}
|
component={TextField}
|
||||||
name="name"
|
defaultValue={entryToEdit ? entryToEdit.entry.name : undefined}
|
||||||
placeholder={entryToEdit ? 'Entry name' : 'New entry'}
|
name="name"
|
||||||
testId={CREATE_ENTRY_INPUT_NAME_ID}
|
placeholder={entryToEdit ? 'Entry name' : 'New entry'}
|
||||||
text={entryToEdit ? 'Entry*' : 'New entry*'}
|
testId={CREATE_ENTRY_INPUT_NAME_ID}
|
||||||
type="text"
|
text={entryToEdit ? 'Entry*' : 'New entry*'}
|
||||||
validate={composeValidators(required, minMaxLength(1, 50))}
|
type="text"
|
||||||
/>
|
validate={composeValidators(required, minMaxLength(1, 50))}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row margin="md">
|
<Row margin="md">
|
||||||
<AddressInput
|
<Col xs={11}>
|
||||||
className={classes.addressInput}
|
<AddressInput
|
||||||
defaultValue={entryToEdit ? entryToEdit.entry.address : undefined}
|
className={classes.addressInput}
|
||||||
disabled={!!entryToEdit}
|
defaultValue={entryToEdit ? entryToEdit.entry.address : undefined}
|
||||||
fieldMutator={mutators.setOwnerAddress}
|
disabled={!!entryToEdit}
|
||||||
name="address"
|
fieldMutator={mutators.setOwnerAddress}
|
||||||
placeholder="Owner address*"
|
name="address"
|
||||||
testId={CREATE_ENTRY_INPUT_ADDRESS_ID}
|
placeholder="Owner address*"
|
||||||
text="Owner address*"
|
testId={CREATE_ENTRY_INPUT_ADDRESS_ID}
|
||||||
validators={entryToEdit ? undefined : [entryDoesntExist]}
|
text="Owner address*"
|
||||||
/>
|
validators={entryToEdit ? undefined : [entryDoesntExist]}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
{!entryToEdit ? (
|
||||||
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
|
</Col>
|
||||||
|
) : null}
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
|
|
@ -3,7 +3,14 @@ import axios from 'axios'
|
||||||
|
|
||||||
import appsIconSvg from '~/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg'
|
import appsIconSvg from '~/routes/safe/components/Transactions/TxsTable/TxType/assets/appsIcon.svg'
|
||||||
|
|
||||||
const gnosisAppsUrl = process.env.REACT_APP_GNOSIS_APPS_URL
|
const removeLastTrailingSlash = (url: string) => {
|
||||||
|
if (url.substr(-1) === '/') {
|
||||||
|
return url.substr(0, url.length - 1)
|
||||||
|
}
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
const gnosisAppsUrl = removeLastTrailingSlash(process.env.REACT_APP_GNOSIS_APPS_URL)
|
||||||
export const staticAppsList = [
|
export const staticAppsList = [
|
||||||
{ url: `${gnosisAppsUrl}/compound`, disabled: false },
|
{ url: `${gnosisAppsUrl}/compound`, disabled: false },
|
||||||
{ url: `${gnosisAppsUrl}/aave`, disabled: false },
|
{ url: `${gnosisAppsUrl}/aave`, disabled: false },
|
||||||
|
@ -30,10 +37,7 @@ export const getAppInfoFromUrl = async (appUrl: string) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
res.url = appUrl.trim()
|
res.url = appUrl.trim()
|
||||||
let noTrailingSlashUrl = res.url
|
let noTrailingSlashUrl = removeLastTrailingSlash(res.url)
|
||||||
if (noTrailingSlashUrl.substr(-1) === '/') {
|
|
||||||
noTrailingSlashUrl = noTrailingSlashUrl.substr(0, noTrailingSlashUrl.length - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const appInfo = await axios.get(`${noTrailingSlashUrl}/manifest.json`)
|
const appInfo = await axios.get(`${noTrailingSlashUrl}/manifest.json`)
|
||||||
|
|
|
@ -9,20 +9,21 @@ import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
import QRIcon from '~/assets/icons/qrcode.svg'
|
|
||||||
import CopyBtn from '~/components/CopyBtn'
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import ScanQRModal from '~/components/ScanQRModal'
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
import WhenFieldChanges from '~/components/forms/WhenFieldChanges'
|
import WhenFieldChanges from '~/components/forms/WhenFieldChanges'
|
||||||
import Block from '~/components/layout/Block'
|
import Block from '~/components/layout/Block'
|
||||||
import Button from '~/components/layout/Button'
|
import Button from '~/components/layout/Button'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
|
import { getAddressBook } from '~/logic/addressBook/store/selectors'
|
||||||
|
import { getNameFromAdbk } from '~/logic/addressBook/utils'
|
||||||
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
|
import type { NFTAssetsState, NFTTokensState } from '~/logic/collectibles/store/reducer/collectibles'
|
||||||
import { nftTokensSelector, safeActiveSelectorMap } from '~/logic/collectibles/store/selectors'
|
import { nftTokensSelector, safeActiveSelectorMap } from '~/logic/collectibles/store/selectors'
|
||||||
import type { NFTToken } from '~/routes/safe/components/Balances/Collectibles/types'
|
import type { NFTToken } from '~/routes/safe/components/Balances/Collectibles/types'
|
||||||
|
@ -60,7 +61,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
const nftAssets: NFTAssetsState = useSelector(safeActiveSelectorMap)
|
const nftAssets: NFTAssetsState = useSelector(safeActiveSelectorMap)
|
||||||
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
|
const nftTokens: NFTTokensState = useSelector(nftTokensSelector)
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
const addressBook: AddressBook = useSelector(getAddressBook)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
||||||
address: recipientAddress || initialValues.recipientAddress,
|
address: recipientAddress || initialValues.recipientAddress,
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -85,14 +86,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
onNext(values)
|
onNext(values)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openQrModal = () => {
|
|
||||||
setQrModalOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeQrModal = () => {
|
|
||||||
setQrModalOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row align="center" className={classes.heading} grow>
|
<Row align="center" className={classes.heading} grow>
|
||||||
|
@ -112,14 +105,18 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
const { assetAddress } = formState.values
|
const { assetAddress } = formState.values
|
||||||
const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress)
|
const selectedNFTTokens = nftTokens.filter((nftToken) => nftToken.assetAddress === assetAddress)
|
||||||
|
|
||||||
const handleScan = (value) => {
|
const handleScan = (value, closeQrModal) => {
|
||||||
let scannedAddress = value
|
let scannedAddress = value
|
||||||
|
|
||||||
if (scannedAddress.startsWith('ethereum:')) {
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
scannedAddress = scannedAddress.replace('ethereum:', '')
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
}
|
}
|
||||||
|
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
|
||||||
mutators.setRecipient(scannedAddress)
|
mutators.setRecipient(scannedAddress)
|
||||||
|
setSelectedEntry({
|
||||||
|
name: scannedName,
|
||||||
|
address: scannedAddress,
|
||||||
|
})
|
||||||
closeQrModal()
|
closeQrModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,16 +197,7 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col center="xs" className={classes} middle="xs" xs={1}>
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
<Img
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
alt="Scan QR"
|
|
||||||
className={classes.qrCodeBtn}
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
openQrModal()
|
|
||||||
}}
|
|
||||||
role="button"
|
|
||||||
src={QRIcon}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
|
@ -256,7 +244,6 @@ const SendCollectible = ({ initialValues, onClose, onNext, recipientAddress, sel
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -10,11 +10,10 @@ import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
import QRIcon from '~/assets/icons/qrcode.svg'
|
|
||||||
import CopyBtn from '~/components/CopyBtn'
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import ScanQRModal from '~/components/ScanQRModal'
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
|
@ -25,9 +24,11 @@ import Button from '~/components/layout/Button'
|
||||||
import ButtonLink from '~/components/layout/ButtonLink'
|
import ButtonLink from '~/components/layout/ButtonLink'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
|
import { getAddressBook } from '~/logic/addressBook/store/selectors'
|
||||||
|
import { getNameFromAdbk } from '~/logic/addressBook/utils'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
||||||
import { safeSelector } from '~/routes/safe/store/selectors'
|
import { safeSelector } from '~/routes/safe/store/selectors'
|
||||||
|
@ -45,13 +46,13 @@ const useStyles = makeStyles(styles)
|
||||||
const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => {
|
const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Props) => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
|
||||||
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
||||||
address: recipientAddress || initialValues.recipientAddress,
|
address: recipientAddress || initialValues.recipientAddress,
|
||||||
name: '',
|
name: '',
|
||||||
})
|
})
|
||||||
const [pristine, setPristine] = useState<boolean>(true)
|
const [pristine, setPristine] = useState<boolean>(true)
|
||||||
const [isValidAddress, setIsValidAddress] = useState<boolean>(true)
|
const [isValidAddress, setIsValidAddress] = useState<boolean>(true)
|
||||||
|
const addressBook: AddressBook = useSelector(getAddressBook)
|
||||||
|
|
||||||
React.useMemo(() => {
|
React.useMemo(() => {
|
||||||
if (selectedEntry === null && pristine) {
|
if (selectedEntry === null && pristine) {
|
||||||
|
@ -65,14 +66,6 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const openQrModal = () => {
|
|
||||||
setQrModalOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeQrModal = () => {
|
|
||||||
setQrModalOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const formMutators = {
|
const formMutators = {
|
||||||
setMax: (args, state, utils) => {
|
setMax: (args, state, utils) => {
|
||||||
utils.changeValue(state, 'value', () => ethBalance)
|
utils.changeValue(state, 'value', () => ethBalance)
|
||||||
|
@ -103,14 +96,18 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
|
||||||
shouldDisableSubmitButton = !selectedEntry.address
|
shouldDisableSubmitButton = !selectedEntry.address
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScan = (value) => {
|
const handleScan = (value, closeQrModal) => {
|
||||||
let scannedAddress = value
|
let scannedAddress = value
|
||||||
|
|
||||||
if (scannedAddress.startsWith('ethereum:')) {
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
scannedAddress = scannedAddress.replace('ethereum:', '')
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
}
|
}
|
||||||
|
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
|
||||||
mutators.setRecipient(scannedAddress)
|
mutators.setRecipient(scannedAddress)
|
||||||
|
setSelectedEntry({
|
||||||
|
name: scannedName,
|
||||||
|
address: scannedAddress,
|
||||||
|
})
|
||||||
closeQrModal()
|
closeQrModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,16 +181,7 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col center="xs" className={classes} middle="xs" xs={1}>
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
<Img
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
alt="Scan QR"
|
|
||||||
className={classes.qrCodeBtn}
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
openQrModal()
|
|
||||||
}}
|
|
||||||
role="button"
|
|
||||||
src={QRIcon}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
|
@ -252,7 +240,6 @@ const SendCustomTx = ({ initialValues, onClose, onNext, recipientAddress }: Prop
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -11,11 +11,10 @@ import ArrowDown from '../assets/arrow-down.svg'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
import QRIcon from '~/assets/icons/qrcode.svg'
|
|
||||||
import CopyBtn from '~/components/CopyBtn'
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
import ScanQRModal from '~/components/ScanQRModal'
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
import TextField from '~/components/forms/TextField'
|
import TextField from '~/components/forms/TextField'
|
||||||
|
@ -25,9 +24,11 @@ import Button from '~/components/layout/Button'
|
||||||
import ButtonLink from '~/components/layout/ButtonLink'
|
import ButtonLink from '~/components/layout/ButtonLink'
|
||||||
import Col from '~/components/layout/Col'
|
import Col from '~/components/layout/Col'
|
||||||
import Hairline from '~/components/layout/Hairline'
|
import Hairline from '~/components/layout/Hairline'
|
||||||
import Img from '~/components/layout/Img'
|
|
||||||
import Paragraph from '~/components/layout/Paragraph'
|
import Paragraph from '~/components/layout/Paragraph'
|
||||||
import Row from '~/components/layout/Row'
|
import Row from '~/components/layout/Row'
|
||||||
|
import type { AddressBook } from '~/logic/addressBook/model/addressBook'
|
||||||
|
import { getAddressBook } from '~/logic/addressBook/store/selectors'
|
||||||
|
import { getNameFromAdbk } from '~/logic/addressBook/utils'
|
||||||
import { type Token } from '~/logic/tokens/store/model/token'
|
import { type Token } from '~/logic/tokens/store/model/token'
|
||||||
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
import SafeInfo from '~/routes/safe/components/Balances/SendModal/SafeInfo'
|
||||||
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
import AddressBookInput from '~/routes/safe/components/Balances/SendModal/screens/AddressBookInput'
|
||||||
|
@ -62,7 +63,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
const { address: safeAddress, ethBalance, name: safeName } = useSelector(safeSelector)
|
||||||
const tokens: Token = useSelector(extendedSafeTokensSelector)
|
const tokens: Token = useSelector(extendedSafeTokensSelector)
|
||||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
const addressBook: AddressBook = useSelector(getAddressBook)
|
||||||
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
const [selectedEntry, setSelectedEntry] = useState<Object | null>({
|
||||||
address: recipientAddress || initialValues.recipientAddress,
|
address: recipientAddress || initialValues.recipientAddress,
|
||||||
name: '',
|
name: '',
|
||||||
|
@ -85,14 +86,6 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
onNext(submitValues)
|
onNext(submitValues)
|
||||||
}
|
}
|
||||||
|
|
||||||
const openQrModal = () => {
|
|
||||||
setQrModalOpen(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const closeQrModal = () => {
|
|
||||||
setQrModalOpen(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Row align="center" className={classes.heading} grow>
|
<Row align="center" className={classes.heading} grow>
|
||||||
|
@ -112,14 +105,18 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
const { token: tokenAddress } = formState.values
|
const { token: tokenAddress } = formState.values
|
||||||
const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress)
|
const selectedTokenRecord = tokens.find((token) => token.address === tokenAddress)
|
||||||
|
|
||||||
const handleScan = (value) => {
|
const handleScan = (value, closeQrModal) => {
|
||||||
let scannedAddress = value
|
let scannedAddress = value
|
||||||
|
|
||||||
if (scannedAddress.startsWith('ethereum:')) {
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
scannedAddress = scannedAddress.replace('ethereum:', '')
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
}
|
}
|
||||||
|
const scannedName = addressBook ? getNameFromAdbk(addressBook, scannedAddress) : ''
|
||||||
mutators.setRecipient(scannedAddress)
|
mutators.setRecipient(scannedAddress)
|
||||||
|
setSelectedEntry({
|
||||||
|
name: scannedName,
|
||||||
|
address: scannedAddress,
|
||||||
|
})
|
||||||
closeQrModal()
|
closeQrModal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,16 +195,7 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col center="xs" className={classes} middle="xs" xs={1}>
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
<Img
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
alt="Scan QR"
|
|
||||||
className={classes.qrCodeBtn}
|
|
||||||
height={20}
|
|
||||||
onClick={() => {
|
|
||||||
openQrModal()
|
|
||||||
}}
|
|
||||||
role="button"
|
|
||||||
src={QRIcon}
|
|
||||||
/>
|
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
|
@ -276,7 +264,6 @@ const SendFunds = ({ initialValues, onClose, onNext, recipientAddress, selectedT
|
||||||
Review
|
Review
|
||||||
</Button>
|
</Button>
|
||||||
</Row>
|
</Row>
|
||||||
{qrModalOpen && <ScanQRModal isOpen={qrModalOpen} onClose={closeQrModal} onScan={handleScan} />}
|
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import { styles } from './style'
|
import { styles } from './style'
|
||||||
|
|
||||||
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import AddressInput from '~/components/forms/AddressInput'
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
|
@ -59,6 +60,16 @@ const OwnerForm = ({ classes, onClose, onSubmit }: Props) => {
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
const mutators = args[3]
|
const mutators = args[3]
|
||||||
|
|
||||||
|
const handleScan = (value, closeQrModal) => {
|
||||||
|
let scannedAddress = value
|
||||||
|
|
||||||
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
|
}
|
||||||
|
mutators.setOwnerAddress(scannedAddress)
|
||||||
|
closeQrModal()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.formContainer}>
|
<Block className={classes.formContainer}>
|
||||||
|
@ -91,6 +102,9 @@ const OwnerForm = ({ classes, onClose, onSubmit }: Props) => {
|
||||||
validators={[ownerDoesntExist]}
|
validators={[ownerDoesntExist]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
|
|
@ -11,6 +11,7 @@ import { styles } from './style'
|
||||||
import CopyBtn from '~/components/CopyBtn'
|
import CopyBtn from '~/components/CopyBtn'
|
||||||
import EtherscanBtn from '~/components/EtherscanBtn'
|
import EtherscanBtn from '~/components/EtherscanBtn'
|
||||||
import Identicon from '~/components/Identicon'
|
import Identicon from '~/components/Identicon'
|
||||||
|
import { ScanQRWrapper } from '~/components/ScanQRModal/ScanQRWrapper'
|
||||||
import AddressInput from '~/components/forms/AddressInput'
|
import AddressInput from '~/components/forms/AddressInput'
|
||||||
import Field from '~/components/forms/Field'
|
import Field from '~/components/forms/Field'
|
||||||
import GnoForm from '~/components/forms/GnoForm'
|
import GnoForm from '~/components/forms/GnoForm'
|
||||||
|
@ -65,6 +66,17 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }: Prop
|
||||||
{(...args) => {
|
{(...args) => {
|
||||||
const mutators = args[3]
|
const mutators = args[3]
|
||||||
|
|
||||||
|
const handleScan = (value, closeQrModal) => {
|
||||||
|
let scannedAddress = value
|
||||||
|
|
||||||
|
if (scannedAddress.startsWith('ethereum:')) {
|
||||||
|
scannedAddress = scannedAddress.replace('ethereum:', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
mutators.setOwnerAddress(scannedAddress)
|
||||||
|
closeQrModal()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Block className={classes.formContainer}>
|
<Block className={classes.formContainer}>
|
||||||
|
@ -126,6 +138,9 @@ const OwnerForm = ({ classes, onClose, onSubmit, ownerAddress, ownerName }: Prop
|
||||||
validators={[ownerDoesntExist]}
|
validators={[ownerDoesntExist]}
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
|
<Col center="xs" className={classes} middle="xs" xs={1}>
|
||||||
|
<ScanQRWrapper handleScan={handleScan} />
|
||||||
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
</Block>
|
</Block>
|
||||||
<Hairline />
|
<Hairline />
|
||||||
|
|
Loading…
Reference in New Issue