From 0ea73c4edaab606c1ae75b3bfdb27716dd96e205 Mon Sep 17 00:00:00 2001 From: Agustin Pane Date: Fri, 5 Feb 2021 05:17:25 -0300 Subject: [PATCH] (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 Co-authored-by: Daniel Sanchez --- src/components/layout/Paragraph/index.tsx | 37 +++++++---- src/routes/open/components/Layout.tsx | 10 +-- src/routes/open/container/Open.tsx | 78 ++++++++++++++++------- src/routes/opening/index.tsx | 40 +++++++----- src/test/builder/safe.redux.builder.ts | 1 + tsconfig.json | 3 +- 6 files changed, 108 insertions(+), 61 deletions(-) diff --git a/src/components/layout/Paragraph/index.tsx b/src/components/layout/Paragraph/index.tsx index 2666974c..da0d5705 100644 --- a/src/components/layout/Paragraph/index.tsx +++ b/src/components/layout/Paragraph/index.tsx @@ -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 { - 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 + style?: CSSProperties +} - return ( -

- {children} -

- ) - } +const Paragraph = (props: Props): ReactElement => { + const { align, children, className, color, dot, noMargin, size, transform, weight, ...restProps } = props + return ( +

+ {children} +

+ ) } export default Paragraph diff --git a/src/routes/open/components/Layout.tsx b/src/routes/open/components/Layout.tsx index 2bce3846..9620c05b 100644 --- a/src/routes/open/components/Layout.tsx +++ b/src/routes/open/components/Layout.tsx @@ -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 diff --git a/src/routes/open/container/Open.tsx b/src/routes/open/container/Open.tsx index 62aa7fc2..8b5df977 100644 --- a/src/routes/open/container/Open.tsx +++ b/src/routes/open/container/Open.tsx @@ -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 => { 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>() - const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState() - const [safePropsFromUrl, setSafePropsFromUrl] = useState() + const [safeCreationPendingInfo, setSafeCreationPendingInfo] = useState<{ txHash?: string } | undefined>() + const [safePropsFromUrl, setSafePropsFromUrl] = useState() 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 ( {showProgress ? ( - (p.inverseColors ? connected : background)}; - color: ${(p) => (p.inverseColors ? background : connected)}; + +interface FullParagraphProps { + inversecolors: string +} + +const FullParagraph = styled(Paragraph)` + 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 -// submittedPromise: Promise -// onRetry: () => void -// onSuccess: () => void -// onCancel: () => void -// } +type Props = { + creationTxHash?: string + submittedPromise?: PromiEvent + 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 {!error && stepIndex <= 4 && Loader dots} - + {error ? 'You can Cancel or Retry the Safe creation process.' : steps[stepIndex].instruction} @@ -350,5 +360,3 @@ const SafeDeployment = ({ creationTxHash, onCancel, onRetry, onSuccess, submitte ) } - -export default SafeDeployment diff --git a/src/test/builder/safe.redux.builder.ts b/src/test/builder/safe.redux.builder.ts index 8a44555a..a9478064 100644 --- a/src/test/builder/safe.redux.builder.ts +++ b/src/test/builder/safe.redux.builder.ts @@ -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) { diff --git a/tsconfig.json b/tsconfig.json index 853eca4a..e3d810ef 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,8 @@ "isolatedModules": true, "noEmit": true, "jsx": "react", - "noFallthroughCasesInSwitch": true + "noFallthroughCasesInSwitch": true, + "downlevelIteration": true }, "paths": { "src/*": [