(Fix) - #1798 broken safe creation deeplink (#1840)

* Add types for SafeProps
Adds getSafePropsValuesFromQueryParams implementation
Replaces window.location with useLocation hook

* Replaces SafeProps with import in Layout.tsx
Adds downlevelIteration to tsconfig.json to allow array.entries()

* Type createSafe()

* SafeDeployment Types

* Type Paragraph and refactor to functional component

* Fix validateQueryParams and types

Co-authored-by: nicolas <nicosampler@users.noreply.github.com>
Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>
This commit is contained in:
Agustin Pane 2021-02-05 05:17:25 -03:00 committed by GitHub
parent d47bd1fe98
commit 0ea73c4eda
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 61 deletions

View File

@ -1,23 +1,34 @@
import classNames from 'classnames/bind'
import * as React from 'react'
import React, { MouseEventHandler, CSSProperties, ReactElement, ReactNode } from 'react'
import styles from './index.module.scss'
const cx = classNames.bind(styles)
class Paragraph extends React.PureComponent<any> {
render() {
const { align, children, className, color, dot, noMargin, size, transform, weight, ...props } = this.props
interface Props {
align?: string
children: ReactNode
className?: string
color?: string
dot?: string
noMargin?: boolean
size?: string
transform?: string
weight?: string
onClick?: MouseEventHandler<HTMLParagraphElement>
style?: CSSProperties
}
return (
<p
className={cx(styles.paragraph, className, weight, { noMargin, dot }, size, color, transform, align)}
{...props}
>
{children}
</p>
)
}
const Paragraph = (props: Props): ReactElement => {
const { align, children, className, color, dot, noMargin, size, transform, weight, ...restProps } = props
return (
<p
className={cx(styles.paragraph, className, weight, { noMargin, dot }, size, color, transform, align)}
{...restProps}
>
{children}
</p>
)
}
export default Paragraph

View File

@ -24,19 +24,13 @@ import { networkSelector, providerNameSelector, userAccountSelector } from 'src/
import { useSelector } from 'react-redux'
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
import { SafeProps } from 'src/routes/open/container/Open'
const { useEffect } = React
const getSteps = () => ['Name', 'Owners and confirmations', 'Review']
type SafeProps = {
name: string
ownerAddresses: any
ownerNames: string
threshold: string
}
type InitialValuesForm = {
export type InitialValuesForm = {
owner0Address?: string
owner0Name?: string
confirmations: string

View File

@ -3,8 +3,8 @@ import queryString from 'query-string'
import React, { useEffect, useState } from 'react'
import ReactGA from 'react-ga'
import { useDispatch, useSelector } from 'react-redux'
import Opening from 'src/routes/opening'
import { Layout } from 'src/routes/open/components/Layout'
import { SafeDeployment } from 'src/routes/opening'
import { InitialValuesForm, Layout } from 'src/routes/open/components/Layout'
import Page from 'src/components/layout/Page'
import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts'
import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions'
@ -24,21 +24,51 @@ import { userAccountSelector } from 'src/logic/wallets/store/selectors'
import { SafeRecordProps } from 'src/logic/safe/store/models/safe'
import { addOrUpdateSafe } from 'src/logic/safe/store/actions/addOrUpdateSafe'
import { PromiEvent, TransactionReceipt } from 'web3-core'
import { useLocation } from 'react-router-dom'
const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY'
const validateQueryParams = (ownerAddresses, ownerNames, threshold, safeName) => {
interface SafeCreationQueryParams {
ownerAddresses: string | string[] | null
ownerNames: string | string[] | null
threshold: number | null
safeName: string | null
}
export interface SafeProps {
name: string
ownerAddresses: string[]
ownerNames: string[]
threshold: string
}
const validateQueryParams = (queryParams: SafeCreationQueryParams): boolean => {
const { ownerAddresses, ownerNames, threshold, safeName } = queryParams
if (!ownerAddresses || !ownerNames || !threshold || !safeName) {
return false
}
if (!ownerAddresses.length || ownerNames.length === 0) {
if (Number.isNaN(threshold)) {
return false
}
if (Number.isNaN(Number(threshold))) {
return false
return threshold > 0 && threshold <= ownerAddresses.length
}
const getSafePropsValuesFromQueryParams = (queryParams: SafeCreationQueryParams): SafeProps | undefined => {
if (!validateQueryParams(queryParams)) {
return
}
const { threshold, safeName, ownerAddresses, ownerNames } = queryParams
return {
name: safeName as string,
threshold: (threshold as number).toString(),
ownerAddresses: Array.isArray(ownerAddresses) ? ownerAddresses : [ownerAddresses as string],
ownerNames: Array.isArray(ownerNames) ? ownerNames : [ownerNames as string],
}
return threshold <= ownerAddresses.length
}
export const getSafeProps = async (
@ -54,7 +84,7 @@ export const getSafeProps = async (
return safeProps
}
export const createSafe = (values, userAccount) => {
export const createSafe = (values: InitialValuesForm, userAccount: string): PromiEvent<TransactionReceipt> => {
const confirmations = getThresholdFrom(values)
const name = getSafeNameFrom(values)
const ownersNames = getNamesFrom(values)
@ -86,24 +116,26 @@ const Open = (): React.ReactElement => {
const [loading, setLoading] = useState(false)
const [showProgress, setShowProgress] = useState(false)
const [creationTxPromise, setCreationTxPromise] = useState<PromiEvent<TransactionReceipt>>()
const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<any>()
const [safePropsFromUrl, setSafePropsFromUrl] = useState()
const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<{ txHash?: string } | undefined>()
const [safePropsFromUrl, setSafePropsFromUrl] = useState<SafeProps | undefined>()
const userAccount = useSelector(userAccountSelector)
const dispatch = useDispatch()
const location = useLocation()
useEffect(() => {
// #122: Allow to migrate an old Multisig by passing the parameters to the URL.
const query = queryString.parse(window.location.search, { arrayFormat: 'comma' })
const query = queryString.parse(location.search, { arrayFormat: 'comma' })
const { name, owneraddresses, ownernames, threshold } = query
if (validateQueryParams(owneraddresses, ownernames, threshold, name)) {
setSafePropsFromUrl({
name,
ownerAddresses: owneraddresses,
ownerNames: ownernames,
threshold,
} as any)
}
}, [])
const safeProps = getSafePropsValuesFromQueryParams({
ownerAddresses: owneraddresses,
ownerNames: ownernames,
threshold: Number(threshold),
safeName: name as string | null,
})
setSafePropsFromUrl(safeProps)
}, [location])
// check if there is a safe being created
useEffect(() => {
@ -121,7 +153,7 @@ const Open = (): React.ReactElement => {
load()
}, [])
const createSafeProxy = async (formValues?: any) => {
const createSafeProxy = async (formValues?: InitialValuesForm) => {
let values = formValues
// save form values, used when the user rejects the TX and wants to retry
@ -132,7 +164,7 @@ const Open = (): React.ReactElement => {
values = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY)
}
const promiEvent = createSafe(values, userAccount)
const promiEvent = createSafe(values as InitialValuesForm, userAccount)
setCreationTxPromise(promiEvent)
setShowProgress(true)
}
@ -186,7 +218,7 @@ const Open = (): React.ReactElement => {
return (
<Page>
{showProgress ? (
<Opening
<SafeDeployment
creationTxHash={safeCreationPendingInfo?.txHash}
onCancel={onCancel}
onRetry={onRetry}

View File

@ -20,6 +20,7 @@ import LoaderDotsSvg from './assets/loader-dots.svg'
import SuccessSvg from './assets/success.svg'
import VaultErrorSvg from './assets/vault-error.svg'
import VaultSvg from './assets/vault.svg'
import { PromiEvent, TransactionReceipt } from 'web3-core'
const Wrapper = styled.div`
display: grid;
@ -56,13 +57,17 @@ const Body = styled.div`
const CardTitle = styled.div`
font-size: 20px;
`
const FullParagraph = styled(Paragraph)`
background-color: ${(p) => (p.inverseColors ? connected : background)};
color: ${(p) => (p.inverseColors ? background : connected)};
interface FullParagraphProps {
inversecolors: string
}
const FullParagraph = styled(Paragraph)<FullParagraphProps>`
background-color: ${(p) => (p.inversecolors ? connected : background)};
color: ${(p) => (p.inversecolors ? background : connected)};
padding: 24px;
font-size: 16px;
margin-bottom: 16px;
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
`
@ -95,16 +100,21 @@ const BackButton = styled(Button)`
margin: 20px auto 0;
`
// type Props = {
// provider: string
// creationTxHash: Promise<any>
// submittedPromise: Promise<any>
// onRetry: () => void
// onSuccess: () => void
// onCancel: () => void
// }
type Props = {
creationTxHash?: string
submittedPromise?: PromiEvent<TransactionReceipt>
onRetry: () => void
onSuccess: (createdSafeAddress: string) => void
onCancel: () => void
}
const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submittedPromise }): React.ReactElement => {
export const SafeDeployment = ({
creationTxHash,
onCancel,
onRetry,
onSuccess,
submittedPromise,
}: Props): React.ReactElement => {
const [loading, setLoading] = useState(true)
const [stepIndex, setStepIndex] = useState(0)
const [safeCreationTxHash, setSafeCreationTxHash] = useState('')
@ -326,7 +336,7 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submitte
<BodyLoader>{!error && stepIndex <= 4 && <Img alt="Loader dots" src={LoaderDotsSvg} />}</BodyLoader>
<BodyInstruction>
<FullParagraph color="primary" inverseColors={confirmationStep} noMargin size="md">
<FullParagraph color="primary" inversecolors={confirmationStep.toString()} noMargin size="md">
{error ? 'You can Cancel or Retry the Safe creation process.' : steps[stepIndex].instruction}
</FullParagraph>
</BodyInstruction>
@ -350,5 +360,3 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submitte
</Wrapper>
)
}
export default SafeDeployment

View File

@ -82,6 +82,7 @@ export const aMinedSafe = async (
[FIELD_NAME]: name,
[FIELD_CONFIRMATIONS]: `${threshold}`,
[FIELD_OWNERS]: `${owners}`,
safeCreationSalt: 0
}
for (let i = 0; i < owners; i += 1) {

View File

@ -21,7 +21,8 @@
"isolatedModules": true,
"noEmit": true,
"jsx": "react",
"noFallthroughCasesInSwitch": true
"noFallthroughCasesInSwitch": true,
"downlevelIteration": true
},
"paths": {
"src/*": [