* Adds query-string package.json Parses query string on open layout * Implements load all the values on openSafe view from param querys * Adds query params validation * Moves query parse logic to open.jsx * Changes default no metamask component on open page * Replaces global isNaN * Fix threshold parsing validation * Updates the welcome component with new verbiage for open * Renames isOpenSafe to isOldMultisigMigration * Merge branch 'development' of https://github.com/gnosis/safe-react into 122-multisig-migration # Conflicts: # src/routes/open/components/Layout.jsx * Merge branch 'development' of https://github.com/gnosis/safe-react into 159-pending-transactions # Conflicts: # src/routes/safe/components/Transactions/index.jsx # yarn.lock
This commit is contained in:
parent
f0b3172abe
commit
e7ba5e5392
|
@ -54,6 +54,7 @@
|
|||
"optimize-css-assets-webpack-plugin": "5.0.3",
|
||||
"polished": "^3.4.2",
|
||||
"qrcode.react": "1.0.0",
|
||||
"query-string": "^6.9.0",
|
||||
"react": "16.12.0",
|
||||
"react-dev-utils": "^10.0.0",
|
||||
"react-dom": "16.12.0",
|
||||
|
|
|
@ -19,6 +19,7 @@ type Props = {
|
|||
testId?: string,
|
||||
validators?: Function[],
|
||||
inputAdornment?: React.Element,
|
||||
defaultValue?: string,
|
||||
}
|
||||
|
||||
const isValidEnsName = (name) => /^([\w-]+\.)+(eth|test|xyz|luxe)$/.test(name)
|
||||
|
@ -35,6 +36,7 @@ const AddressInput = ({
|
|||
testId,
|
||||
inputAdornment,
|
||||
validators = [],
|
||||
defaultValue,
|
||||
}: Props): React.Element<*> => (
|
||||
<>
|
||||
<Field
|
||||
|
@ -51,6 +53,7 @@ const AddressInput = ({
|
|||
text={text}
|
||||
className={className}
|
||||
testId={testId}
|
||||
defaultValue={defaultValue}
|
||||
/>
|
||||
<OnChange name={name}>
|
||||
{async (value) => {
|
||||
|
|
|
@ -9,23 +9,54 @@ import Row from '~/components/layout/Row'
|
|||
import Review from '~/routes/open/components/ReviewInformation'
|
||||
import SafeNameField from '~/routes/open/components/SafeNameForm'
|
||||
import SafeOwnersFields from '~/routes/open/components/SafeOwnersConfirmationsForm'
|
||||
import { getOwnerNameBy, getOwnerAddressBy, FIELD_CONFIRMATIONS } from '~/routes/open/components/fields'
|
||||
import {
|
||||
getOwnerNameBy,
|
||||
getOwnerAddressBy,
|
||||
FIELD_CONFIRMATIONS,
|
||||
FIELD_SAFE_NAME,
|
||||
} from '~/routes/open/components/fields'
|
||||
import { history } from '~/store'
|
||||
import { secondary, sm } from '~/theme/variables'
|
||||
import type { SafePropsType } from '~/routes/open/container/Open'
|
||||
import Welcome from '~/routes/welcome/components/Layout'
|
||||
|
||||
const getSteps = () => ['Name', 'Owners and confirmations', 'Review']
|
||||
|
||||
const initialValuesFrom = (userAccount: string) => ({
|
||||
[getOwnerNameBy(0)]: 'My Wallet',
|
||||
[getOwnerAddressBy(0)]: userAccount,
|
||||
[FIELD_CONFIRMATIONS]: '1',
|
||||
})
|
||||
|
||||
const initialValuesFrom = (userAccount: string, safeProps?: SafePropsType) => {
|
||||
if (!safeProps) {
|
||||
return ({
|
||||
[getOwnerNameBy(0)]: 'My Wallet',
|
||||
[getOwnerAddressBy(0)]: userAccount,
|
||||
[FIELD_CONFIRMATIONS]: '1',
|
||||
})
|
||||
}
|
||||
let obj = {}
|
||||
const {
|
||||
ownerAddresses, ownerNames, threshold, name,
|
||||
} = safeProps
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const [index, value] of ownerAddresses.entries()) {
|
||||
const safeName = ownerNames[index] ? ownerNames[index] : 'My Wallet'
|
||||
obj = {
|
||||
...obj,
|
||||
[getOwnerAddressBy(index)]: value,
|
||||
[getOwnerNameBy(index)]: safeName,
|
||||
}
|
||||
}
|
||||
return ({
|
||||
...obj,
|
||||
[FIELD_CONFIRMATIONS]: threshold || '1',
|
||||
[FIELD_SAFE_NAME]: name,
|
||||
})
|
||||
}
|
||||
|
||||
type Props = {
|
||||
provider: string,
|
||||
userAccount: string,
|
||||
network: string,
|
||||
onCallSafeContractSubmit: (values: Object) => Promise<void>,
|
||||
safeProps?: SafePropsType,
|
||||
}
|
||||
|
||||
const iconStyle = {
|
||||
|
@ -44,11 +75,14 @@ const formMutators = {
|
|||
},
|
||||
}
|
||||
|
||||
const Layout = ({
|
||||
provider, userAccount, onCallSafeContractSubmit, network,
|
||||
}: Props) => {
|
||||
|
||||
const Layout = (props: Props) => {
|
||||
const {
|
||||
provider, userAccount, onCallSafeContractSubmit, network, safeProps,
|
||||
} = props
|
||||
const steps = getSteps()
|
||||
const initialValues = initialValuesFrom(userAccount)
|
||||
|
||||
const initialValues = initialValuesFrom(userAccount, safeProps)
|
||||
|
||||
return (
|
||||
<>
|
||||
|
@ -75,7 +109,7 @@ const Layout = ({
|
|||
</Stepper>
|
||||
</Block>
|
||||
) : (
|
||||
<div>No web3 provider detected</div>
|
||||
<Welcome provider={provider} isOldMultisigMigration />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -12,6 +12,7 @@ import { sm, secondary } from '~/theme/variables'
|
|||
|
||||
type Props = {
|
||||
classes: Object,
|
||||
safeName?: string,
|
||||
}
|
||||
|
||||
const styles = () => ({
|
||||
|
@ -32,12 +33,13 @@ const styles = () => ({
|
|||
},
|
||||
})
|
||||
|
||||
const SafeName = ({ classes }: Props) => (
|
||||
const SafeName = ({ classes, safeName }: Props) => (
|
||||
<>
|
||||
<Block margin="lg">
|
||||
<Paragraph noMargin size="md" color="primary">
|
||||
You are about to create a new Gnosis Safe wallet with one or more owners. First, let's give your new wallet
|
||||
a name. This name is only stored locally and will never be shared with Gnosis or any third parties.
|
||||
You are about to create a new Gnosis Safe wallet with one or more owners. First, let's give your new
|
||||
wallet
|
||||
a name. This name is only stored locally and will never be shared with Gnosis or any third parties.
|
||||
</Paragraph>
|
||||
</Block>
|
||||
<Block margin="lg" className={classes.root}>
|
||||
|
@ -48,23 +50,24 @@ const SafeName = ({ classes }: Props) => (
|
|||
validate={required}
|
||||
placeholder="Name of the new Safe"
|
||||
text="Safe name"
|
||||
defaultValue={safeName}
|
||||
/>
|
||||
</Block>
|
||||
<Block margin="lg">
|
||||
<Paragraph noMargin size="md" color="primary" className={classes.links}>
|
||||
By continuing you consent with the
|
||||
By continuing you consent with the
|
||||
{' '}
|
||||
<a rel="noopener noreferrer" href="https://safe.gnosis.io/terms" target="_blank">
|
||||
terms of use
|
||||
terms of use
|
||||
</a>
|
||||
{' '}
|
||||
and
|
||||
and
|
||||
{' '}
|
||||
<a rel="noopener noreferrer" href="https://safe.gnosis.io/privacy" target="_blank">
|
||||
privacy policy
|
||||
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.
|
||||
. 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>
|
||||
</>
|
||||
|
@ -72,10 +75,13 @@ const SafeName = ({ classes }: Props) => (
|
|||
|
||||
const SafeNameForm = withStyles(styles)(SafeName)
|
||||
|
||||
const SafeNamePage = () => (controls: React.Node) => (
|
||||
<OpenPaper controls={controls}>
|
||||
<SafeNameForm />
|
||||
</OpenPaper>
|
||||
)
|
||||
const SafeNamePage = () => (controls: React.Node, { values }) => {
|
||||
const { safeName } = values
|
||||
return (
|
||||
<OpenPaper controls={controls}>
|
||||
<SafeNameForm safeName={safeName} />
|
||||
</OpenPaper>
|
||||
)
|
||||
}
|
||||
|
||||
export default SafeNamePage
|
||||
|
|
|
@ -4,6 +4,7 @@ import { withStyles } from '@material-ui/core/styles'
|
|||
import InputAdornment from '@material-ui/core/InputAdornment'
|
||||
import CheckCircle from '@material-ui/icons/CheckCircle'
|
||||
import MenuItem from '@material-ui/core/MenuItem'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import Field from '~/components/forms/Field'
|
||||
import TextField from '~/components/forms/TextField'
|
||||
import SelectField from '~/components/forms/SelectField'
|
||||
|
@ -70,6 +71,7 @@ const SafeOwners = (props: Props) => {
|
|||
} = props
|
||||
|
||||
const validOwners = getNumOwnersFrom(values)
|
||||
|
||||
const [numOwners, setNumOwners] = useState<number>(validOwners)
|
||||
const [qrModalOpen, setQrModalOpen] = useState<boolean>(false)
|
||||
const [scanQrForOwnerName, setScanQrForOwnerName] = useState<string | null>(null)
|
||||
|
@ -222,7 +224,7 @@ owner(s)
|
|||
)
|
||||
}
|
||||
|
||||
const SafeOwnersForm = withStyles(styles)(SafeOwners)
|
||||
const SafeOwnersForm = withStyles(styles)(withRouter(SafeOwners))
|
||||
|
||||
const SafeOwnersPage = ({ updateInitialProps }: Object) => (controls: React.Node, { values, errors, form }: Object) => (
|
||||
<>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
export const FIELD_NAME: string = 'name'
|
||||
export const FIELD_CONFIRMATIONS: string = 'confirmations'
|
||||
export const FIELD_OWNERS: string = 'owners'
|
||||
export const FIELD_SAFE_NAME: string = 'safeName'
|
||||
|
||||
export const getOwnerNameBy = (index: number) => `owner${index}Name`
|
||||
export const getOwnerAddressBy = (index: number) => `owner${index}Address`
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// @flow
|
||||
import * as React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import queryString from 'query-string'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import Page from '~/components/layout/Page'
|
||||
import {
|
||||
getAccountsFrom, getThresholdFrom, getNamesFrom, getSafeNameFrom, getOwnersFrom,
|
||||
|
@ -24,6 +26,30 @@ export type OpenState = {
|
|||
safeAddress: string,
|
||||
}
|
||||
|
||||
export type SafePropsType = {
|
||||
name: string,
|
||||
ownerAddresses: string[],
|
||||
ownerNames: string[],
|
||||
threshold: string,
|
||||
}
|
||||
|
||||
const validateQueryParams = (ownerAddresses?: string[], ownerNames?: string[], threshold?: string, safeName?: string) => {
|
||||
if (!ownerAddresses || !ownerNames || !threshold || !safeName) {
|
||||
return false
|
||||
}
|
||||
if (!ownerAddresses.length === 0 || ownerNames.length === 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (Number.isNaN(Number(threshold))) {
|
||||
return false
|
||||
}
|
||||
if (threshold > ownerAddresses.length) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
export const createSafe = async (values: Object, userAccount: string, addSafe: AddSafe): Promise<OpenState> => {
|
||||
const numConfirmations = getThresholdFrom(values)
|
||||
const name = getSafeNameFrom(values)
|
||||
|
@ -75,8 +101,23 @@ class Open extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { provider, userAccount, network } = this.props
|
||||
const {
|
||||
provider, userAccount, network, location,
|
||||
} = this.props
|
||||
const query: SafePropsType = queryString.parse(location.search, { arrayFormat: 'comma' })
|
||||
const {
|
||||
name, owneraddresses, ownernames, threshold,
|
||||
} = query
|
||||
|
||||
let safeProps = null
|
||||
if (validateQueryParams(owneraddresses, ownernames, threshold, name)) {
|
||||
safeProps = {
|
||||
name,
|
||||
ownerAddresses: owneraddresses,
|
||||
ownerNames: ownernames,
|
||||
threshold,
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Page>
|
||||
<Layout
|
||||
|
@ -84,10 +125,11 @@ class Open extends React.Component<Props> {
|
|||
provider={provider}
|
||||
userAccount={userAccount}
|
||||
onCallSafeContractSubmit={this.onCallSafeContractSubmit}
|
||||
safeProps={safeProps}
|
||||
/>
|
||||
</Page>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(selector, actions)(Open)
|
||||
export default connect(selector, actions)(withRouter(Open))
|
||||
|
|
|
@ -15,7 +15,8 @@ const safe = require('../assets/safe.svg')
|
|||
const plus = require('../assets/new.svg')
|
||||
|
||||
type Props = {
|
||||
provider: string
|
||||
provider: string,
|
||||
isOldMultisigMigration?: boolean,
|
||||
}
|
||||
|
||||
const openIconStyle = {
|
||||
|
@ -64,14 +65,20 @@ export const LoadSafe = ({ size, provider }: SafeProps) => (
|
|||
</Button>
|
||||
)
|
||||
|
||||
const Welcome = ({ provider }: Props) => (
|
||||
<Block className={styles.safe}>
|
||||
<Heading tag="h1" weight="bold" align="center" margin="lg">
|
||||
Welcome to
|
||||
|
||||
const Welcome = ({ provider, isOldMultisigMigration }: Props) => {
|
||||
const headingText = isOldMultisigMigration ? (
|
||||
<>
|
||||
We will replicate the owner structure from your existing Gnosis Multisig
|
||||
<br />
|
||||
Gnosis Safe For Teams
|
||||
</Heading>
|
||||
<Heading tag="h3" align="center" margin="xl">
|
||||
to let you test the new interface.
|
||||
<br />
|
||||
As soon as you feel comfortable, start moving funds to your new Safe.
|
||||
<br />
|
||||
{' '}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
Gnosis Safe for Teams is the most secure way to manage crypto funds
|
||||
<br />
|
||||
collectively. It is an improvement of the Gnosis MultiSig, which is used
|
||||
|
@ -85,34 +92,46 @@ const Welcome = ({ provider }: Props) => (
|
|||
design, formally verified smart contracts and vastly improved user
|
||||
experience.
|
||||
{' '}
|
||||
<a
|
||||
className={styles.learnMoreLink}
|
||||
href="https://safe.gnosis.io/teams"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
<OpenInNew style={openIconStyle} />
|
||||
</a>
|
||||
</Heading>
|
||||
{provider ? (
|
||||
<>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<CreateSafe size="large" provider={provider} />
|
||||
</>
|
||||
)
|
||||
return (
|
||||
<Block className={styles.safe}>
|
||||
<Heading tag="h1" weight="bold" align="center" margin="lg">
|
||||
Welcome to
|
||||
<br />
|
||||
Gnosis Safe For Teams
|
||||
</Heading>
|
||||
<Heading tag="h3" align="center" margin="xl">
|
||||
{ headingText }
|
||||
<a
|
||||
className={styles.learnMoreLink}
|
||||
href="https://safe.gnosis.io/teams"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn more
|
||||
<OpenInNew style={openIconStyle} />
|
||||
</a>
|
||||
</Heading>
|
||||
{provider ? (
|
||||
<>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<CreateSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<LoadSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
</>
|
||||
) : (
|
||||
<Block margin="md" className={styles.connectWallet}>
|
||||
<Heading tag="h3" align="center" margin="md">
|
||||
Get Started by Connecting a Wallet
|
||||
</Heading>
|
||||
<ConnectButton minWidth={240} minHeight={42} />
|
||||
</Block>
|
||||
<Block className={styles.safeActions} margin="md">
|
||||
<LoadSafe size="large" provider={provider} />
|
||||
</Block>
|
||||
</>
|
||||
) : (
|
||||
<Block margin="md" className={styles.connectWallet}>
|
||||
<Heading tag="h3" align="center" margin="md">
|
||||
Get Started by Connecting a Wallet
|
||||
</Heading>
|
||||
<ConnectButton minWidth={240} minHeight={42} />
|
||||
</Block>
|
||||
)}
|
||||
</Block>
|
||||
)
|
||||
)}
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
||||
export default Welcome
|
||||
|
|
Loading…
Reference in New Issue