diff --git a/cypress/e2e/smoke/load_safe.cy.js b/cypress/e2e/smoke/load_safe.cy.js index 599d511d6d..144bb30f83 100644 --- a/cypress/e2e/smoke/load_safe.cy.js +++ b/cypress/e2e/smoke/load_safe.cy.js @@ -29,22 +29,20 @@ describe('Load existing Safe', () => { it('should allow choosing the network where the Safe exists', () => { // Click the network selector inside the Stepper content - cy.contains('Select network on which the Safe was created:').contains('span', 'Polygon').click() + cy.get('[data-testid=load-safe-form]').contains('Polygon').click() // Selects Goerli cy.get('ul li') .contains(/^G(ö|oe)rli$/) .click() - cy.contains('Select network on which the Safe was created:').contains('span', /^G(ö|oe)rli$/) - - cy.contains('Continue').click() + cy.contains('span', /^G(ö|oe)rli$/) }) it('should accept name the Safe', () => { // alias the address input label cy.get('input[name="address"]').parent().prev('label').as('addressLabel') - // Name input should have a placeholder ending in 'rinkeby-safe' + // Name input should have a placeholder ending in 'goerli-safe' cy.get('input[name="name"]') .should('have.attr', 'placeholder') .should('match', /g(ö|oe)rli-safe/) @@ -77,7 +75,7 @@ describe('Load existing Safe', () => { const [, address] = SAFE_QR_CODE_ADDRESS.split(':') cy.get('input[name="address"]').should('have.value', address) - cy.contains('Continue').click() + cy.contains('Next').click() }) // TODO: register the goerli ENS for the Safe owner when possible @@ -92,7 +90,7 @@ describe('Load existing Safe', () => { it('should set custom name in the first owner', () => { // Sets a custom name for the first owner cy.get('input[name="owners.0.name"]').type('Test Owner Name').should('have.value', 'Test Owner Name') - cy.contains('Continue').click() + cy.contains('Next').click() }) it('should have Safe and owner names in the Review step', () => { diff --git a/src/components/common/NetworkSelector/index.tsx b/src/components/common/NetworkSelector/index.tsx index 279a754d0d..15d527a5bd 100644 --- a/src/components/common/NetworkSelector/index.tsx +++ b/src/components/common/NetworkSelector/index.tsx @@ -24,7 +24,7 @@ const NetworkSelector = (): ReactElement => { trackEvent({ ...OVERVIEW_EVENTS.SWITCH_NETWORK, label: selectedChainId }) - const shouldKeepPath = [AppRoutes.load, AppRoutes.open, AppRoutes.newSafe.create].includes(router.pathname) + const shouldKeepPath = [AppRoutes.newSafe.create, AppRoutes.newSafe.load].includes(router.pathname) const newRoute = { pathname: shouldKeepPath ? router.pathname : '/', diff --git a/src/components/common/PageLayout/SideDrawer.tsx b/src/components/common/PageLayout/SideDrawer.tsx index 27166ccf47..61e8300f65 100644 --- a/src/components/common/PageLayout/SideDrawer.tsx +++ b/src/components/common/PageLayout/SideDrawer.tsx @@ -20,6 +20,10 @@ const isNewSafeRoute = (pathname: string): boolean => { return pathname === AppRoutes.newSafe.create } +const isLoadSafeRoute = (pathname: string): boolean => { + return pathname === AppRoutes.newSafe.load +} + const isAppShareRoute = (pathname: string): boolean => { return pathname === AppRoutes.share.safeApp } @@ -36,7 +40,11 @@ const SideDrawer = ({ isOpen, onToggle }: SideDrawerProps): ReactElement => { useEffect(() => { const closeSidebar = - isSmallScreen || isSafeAppRoute(pathname, query) || isAppShareRoute(pathname) || isNewSafeRoute(pathname) + isSmallScreen || + isSafeAppRoute(pathname, query) || + isAppShareRoute(pathname) || + isNewSafeRoute(pathname) || + isLoadSafeRoute(pathname) onToggle(!closeSidebar) }, [isSmallScreen, onToggle, pathname, query]) diff --git a/src/components/load-safe/index.tsx b/src/components/load-safe/index.tsx deleted file mode 100644 index d1a273a8c9..0000000000 --- a/src/components/load-safe/index.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React from 'react' -import { useRouter } from 'next/router' - -import type { TxStepperProps } from '@/components/tx/TxStepper/useTxStepper' -import VerticalTxStepper from '@/components/tx/TxStepper/vertical' -import { AppRoutes } from '@/config/routes' -import SafeOwnersStep from '@/components/load-safe/steps/SafeOwnersStep' -import SetAddressStep from '@/components/load-safe/steps/SetAddressStep' -import SafeReviewStep from '@/components/load-safe/steps/SafeReviewStep' -import SelectNetworkStep from '@/components/load-safe/steps/SelectNetworkStep' -import type { SafeFormData } from '@/components/create-safe/types' -import { LOAD_SAFE_CATEGORY } from '@/services/analytics' - -export const LoadSafeSteps: TxStepperProps['steps'] = [ - { - label: 'Connect wallet & select network', - render: (_, onSubmit, onBack) => , - }, - { - label: 'Name and address', - render: (data, onSubmit, onBack) => ( - - ), - }, - { - label: 'Owners', - render: (data, onSubmit, onBack) => ( - - ), - }, - { - label: 'Review', - render: (data, _, onBack) => , - }, -] - -const LoadSafe = ({ - initialStep, - initialData, -}: { - initialStep?: TxStepperProps['initialStep'] - initialData?: TxStepperProps['initialData'] -}) => { - const router = useRouter() - - return ( - router.push(AppRoutes.welcome)} - eventCategory={LOAD_SAFE_CATEGORY} - /> - ) -} - -export default LoadSafe diff --git a/src/components/load-safe/steps/SafeOwnersStep.tsx b/src/components/load-safe/steps/SafeOwnersStep.tsx deleted file mode 100644 index c913336ded..0000000000 --- a/src/components/load-safe/steps/SafeOwnersStep.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import type { ReactElement } from 'react' -import React, { useEffect } from 'react' -import { Box, Button, Divider, Grid, Paper, Typography } from '@mui/material' -import { FormProvider, useFieldArray, useForm } from 'react-hook-form' - -import type { StepRenderProps } from '@/components/tx/TxStepper/useTxStepper' -import ChainIndicator from '@/components/common/ChainIndicator' -import useAsync from '@/hooks/useAsync' -import type { SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' -import { getSafeInfo } from '@safe-global/safe-gateway-typescript-sdk' - -import { OwnerRow } from '@/components/create-safe/steps/OwnerRow' -import useChainId from '@/hooks/useChainId' -import type { SafeFormData } from '@/components/create-safe/types' - -type Props = { - params: SafeFormData - onSubmit: StepRenderProps['onSubmit'] - onBack: StepRenderProps['onBack'] -} - -const SafeOwnersStep = ({ params, onSubmit, onBack }: Props): ReactElement => { - const chainId = useChainId() - const formMethods = useForm({ defaultValues: params, mode: 'onChange' }) - const { handleSubmit, setValue, control, formState, getValues } = formMethods - - const { fields } = useFieldArray({ - control, - name: 'owners', - }) - - const [safeInfo] = useAsync(() => { - if (params.address) { - return getSafeInfo(chainId, params.address) - } - }, [chainId, params.address]) - - useEffect(() => { - if (!safeInfo) return - - setValue('threshold', safeInfo.threshold) - - const owners = safeInfo.owners.map((owner, i) => ({ - address: owner.value, - name: getValues(`owners.${i}.name`) || '', - })) - - setValue('owners', owners) - }, [getValues, safeInfo, setValue]) - - const onFormBack = () => { - onBack(getValues()) - } - - return ( - - -
- - - This Safe on has {safeInfo?.owners.length} owners. Optional: Provide a name for - each owner. - - - - - - Name - - - Address - - - - - {fields.map((field, index) => ( - - ))} - - - - - - - - - - -
-
- ) -} - -export default SafeOwnersStep diff --git a/src/components/load-safe/steps/SafeReviewStep.tsx b/src/components/load-safe/steps/SafeReviewStep.tsx deleted file mode 100644 index 7cf8e73345..0000000000 --- a/src/components/load-safe/steps/SafeReviewStep.tsx +++ /dev/null @@ -1,183 +0,0 @@ -import React from 'react' -import { Box, Button, Divider, Grid, Paper, Typography } from '@mui/material' -import type { StepRenderProps } from '@/components/tx/TxStepper/useTxStepper' -import ChainIndicator from '@/components/common/ChainIndicator' -import EthHashInfo from '@/components/common/EthHashInfo' -import { useAppDispatch } from '@/store' -import { addOrUpdateSafe } from '@/store/addedSafesSlice' -import { useRouter } from 'next/router' -import { AppRoutes } from '@/config/routes' -import { upsertAddressBookEntry } from '@/store/addressBookSlice' -import useWallet from '@/hooks/wallets/useWallet' -import { isOwner } from '@/utils/transaction-guards' -import { defaultSafeInfo } from '@/store/safeInfoSlice' -import { useCurrentChain } from '@/hooks/useChains' -import type { SafeFormData } from '@/components/create-safe/types' -import { trackEvent, LOAD_SAFE_EVENTS } from '@/services/analytics' - -type Props = { - params: SafeFormData - onBack: StepRenderProps['onBack'] -} - -const SafeReviewStep = ({ params, onBack }: Props) => { - const dispatch = useAppDispatch() - const router = useRouter() - const wallet = useWallet() - const isSafeOwner = wallet && isOwner(params.owners, wallet.address) - const currentChain = useCurrentChain() - const chainId = currentChain?.chainId || '' - - const addSafe = () => { - const safeName = params.name - const safeAddress = params.address - - dispatch( - addOrUpdateSafe({ - safe: { - ...defaultSafeInfo, - address: { value: safeAddress, name: safeName }, - threshold: params.threshold, - owners: params.owners.map((owner) => ({ - value: owner.address, - name: owner.name || owner.ens, - })), - chainId, - }, - }), - ) - - dispatch( - upsertAddressBookEntry({ - chainId, - address: safeAddress, - name: safeName, - }), - ) - - for (const { address, name, ens } of params.owners) { - const entryName = name || ens - - if (!entryName) { - continue - } - - dispatch( - upsertAddressBookEntry({ - chainId, - address, - name: entryName, - }), - ) - } - - trackEvent({ - ...LOAD_SAFE_EVENTS.OWNERS, - label: params.owners.length, - }) - - trackEvent({ - ...LOAD_SAFE_EVENTS.THRESHOLD, - label: params.threshold, - }) - - trackEvent(LOAD_SAFE_EVENTS.GO_TO_SAFE) - - router.push({ - pathname: AppRoutes.home, - query: { safe: `${currentChain?.shortName}:${safeAddress}` }, - }) - } - - return ( - - - - - Details - - Network - - - - - - {params.name && ( - <> - - Name of the Safe - - {params.name} - - )} - - Safe address - - - - - - Connected wallet client is owner? - - {isSafeOwner ? 'Yes' : 'No'} - - - Any transaction requires the confirmation of: - - - {params.threshold} out of {params.owners.length} - - - - [undefined, undefined, `1px solid ${palette.border.light}`]} - borderTop={({ palette }) => [`1px solid ${palette.border.light}`, undefined, 'none']} - > - {params.owners.length} Safe owner(s) - - - {params.owners.map((owner) => { - return ( - - - - ) - })} - - - - - - - - - - - - - - - - - ) -} - -export default SafeReviewStep diff --git a/src/components/load-safe/steps/SelectNetworkStep.tsx b/src/components/load-safe/steps/SelectNetworkStep.tsx deleted file mode 100644 index a52df40d18..0000000000 --- a/src/components/load-safe/steps/SelectNetworkStep.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { Box, Button, Divider, Grid, Paper, Typography } from '@mui/material' -import type { StepRenderProps } from '@/components/tx/TxStepper/useTxStepper' -import NetworkSelector from '@/components/common/NetworkSelector' - -type Props = { - onSubmit: StepRenderProps['onSubmit'] - onBack: StepRenderProps['onBack'] -} - -const SelectNetworkStep = ({ onSubmit, onBack }: Props) => { - return ( - - - - Select network on which the Safe was created: - - - - - - - - - - - - - - - ) -} - -export default SelectNetworkStep diff --git a/src/components/load-safe/steps/SetAddressStep.tsx b/src/components/load-safe/steps/SetAddressStep.tsx deleted file mode 100644 index 104471d3f2..0000000000 --- a/src/components/load-safe/steps/SetAddressStep.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import React from 'react' -import { Box, Button, CircularProgress, Divider, Grid, InputAdornment, Paper, Typography } from '@mui/material' -import { useForm, FormProvider } from 'react-hook-form' -import type { StepRenderProps } from '@/components/tx/TxStepper/useTxStepper' -import ChainIndicator from '@/components/common/ChainIndicator' -import AddressInput from '@/components/common/AddressInput' -import { getSafeInfo } from '@safe-global/safe-gateway-typescript-sdk' -import useChainId from '@/hooks/useChainId' -import { useAppSelector } from '@/store' -import { selectAddedSafes } from '@/store/addedSafesSlice' -import NameInput from '@/components/common/NameInput' -import { useAddressResolver } from '@/hooks/useAddressResolver' -import { useMnemonicSafeName } from '@/hooks/useMnemonicName' -import type { SafeFormData } from '@/components/create-safe/types' -import { trackEvent, LOAD_SAFE_EVENTS } from '@/services/analytics' -import ExternalLink from '@/components/common/ExternalLink' - -type Props = { - params: SafeFormData - onSubmit: StepRenderProps['onSubmit'] - onBack: StepRenderProps['onBack'] -} - -enum FormField { - address = 'address', - name = 'name', -} - -const SetAddressStep = ({ params, onSubmit, onBack }: Props) => { - const currentChainId = useChainId() - const addedSafes = useAppSelector((state) => selectAddedSafes(state, currentChainId)) - const formMethods = useForm({ - mode: 'onChange', - defaultValues: { - [FormField.address]: params?.address || '', - [FormField.name]: params?.name || '', - }, - }) - - const { handleSubmit, watch, formState, getValues } = formMethods - - const safeAddress = watch('address') - - const randomName = useMnemonicSafeName() - const { ens, name, resolving } = useAddressResolver(safeAddress) - - // Address book, ENS, mnemonic - const fallbackName = name || ens || randomName - - const validateSafeAddress = async (address: string) => { - if (addedSafes && Object.keys(addedSafes).includes(address)) { - return 'Safe is already added' - } - - try { - await getSafeInfo(currentChainId, address) - } catch (error) { - return 'Address given is not a valid Safe address' - } - } - - const onFormSubmit = handleSubmit((data: SafeFormData) => { - onSubmit({ - ...data, - [FormField.name]: data[FormField.name] || fallbackName, - }) - - if (data[FormField.name]) { - trackEvent(LOAD_SAFE_EVENTS.NAME_SAFE) - } - }) - - const onFormBack = () => { - onBack({ - ...getValues(), - [FormField.name]: getValues([FormField.name]) || fallbackName, - }) - } - - return ( - - -
- - - You are about to add an existing Safe on . First, choose a name and enter the - Safe address. The name is only stored locally and will never be shared with us or any third parties. Your - connected wallet does not have to be the owner of this Safe. In this case, the interface will provide you - a read-only view. - - - - Don't have the address of the Safe you created?{' '} - - This article explains how to find it. - - - - - - - - ), - }} - /> - - - - - - - - By continuing you consent to the{' '} - terms of use and{' '} - privacy policy. - - - - - - - - - - - - - - - - -
-
- ) -} - -export default SetAddressStep diff --git a/src/components/new-safe/steps/Step2/OwnerRow.tsx b/src/components/new-safe/OwnerRow/index.tsx similarity index 88% rename from src/components/new-safe/steps/Step2/OwnerRow.tsx rename to src/components/new-safe/OwnerRow/index.tsx index e582cd560e..8899c1ca50 100644 --- a/src/components/new-safe/steps/Step2/OwnerRow.tsx +++ b/src/components/new-safe/OwnerRow/index.tsx @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo } from 'react' -import { CircularProgress, FormControl, Grid, IconButton, SvgIcon } from '@mui/material' +import { CircularProgress, FormControl, Grid, IconButton, SvgIcon, Typography } from '@mui/material' import NameInput from '@/components/common/NameInput' import InputAdornment from '@mui/material/InputAdornment' import AddressBookInput from '@/components/common/AddressBookInput' @@ -10,11 +10,8 @@ import EthHashInfo from '@/components/common/EthHashInfo' import type { NamedAddress } from '@/components/create-safe/types' import useWallet from '@/hooks/wallets/useWallet' import { sameAddress } from '@/utils/addresses' +import css from './styles.module.css' -/** - * TODO: this is a slightly modified copy of the old /create-safe/OwnerRow.tsx - * Once we remove the old safe creation flow we should remove the old file. - */ export const OwnerRow = ({ index, groupName, @@ -66,10 +63,11 @@ export const OwnerRow = ({ }, [ens, setValue, getValues, name, fieldName]) return ( - - + + {readOnly ? ( - + + + ) : ( ) } + +export default OwnerRow diff --git a/src/components/new-safe/OwnerRow/styles.module.css b/src/components/new-safe/OwnerRow/styles.module.css new file mode 100644 index 0000000000..434db87c10 --- /dev/null +++ b/src/components/new-safe/OwnerRow/styles.module.css @@ -0,0 +1,11 @@ +.name :global .MuiFormHelperText-root { + position: absolute; + bottom: -20px; +} + +@media (max-width: 900px) { + .name :global .MuiFormHelperText-root { + position: relative; + bottom: 0; + } +} diff --git a/src/components/new-safe/ReviewRow/index.tsx b/src/components/new-safe/ReviewRow/index.tsx new file mode 100644 index 0000000000..7478c054c4 --- /dev/null +++ b/src/components/new-safe/ReviewRow/index.tsx @@ -0,0 +1,17 @@ +import React, { type ReactElement } from 'react' +import { Grid, Typography } from '@mui/material' + +const ReviewRow = ({ name, value }: { name: string; value: ReactElement }) => { + return ( + <> + + {name} + + + {value} + + + ) +} + +export default ReviewRow diff --git a/src/components/new-safe/CreateSafeInfos/index.tsx b/src/components/new-safe/create/CreateSafeInfos/index.tsx similarity index 100% rename from src/components/new-safe/CreateSafeInfos/index.tsx rename to src/components/new-safe/create/CreateSafeInfos/index.tsx diff --git a/src/components/new-safe/NetworkWarning/index.tsx b/src/components/new-safe/create/NetworkWarning/index.tsx similarity index 100% rename from src/components/new-safe/NetworkWarning/index.tsx rename to src/components/new-safe/create/NetworkWarning/index.tsx diff --git a/src/components/new-safe/OverviewWidget/index.tsx b/src/components/new-safe/create/OverviewWidget/index.tsx similarity index 95% rename from src/components/new-safe/OverviewWidget/index.tsx rename to src/components/new-safe/create/OverviewWidget/index.tsx index dace52829a..14c02e63c5 100644 --- a/src/components/new-safe/OverviewWidget/index.tsx +++ b/src/components/new-safe/create/OverviewWidget/index.tsx @@ -6,7 +6,7 @@ import { Card, Grid, Typography } from '@mui/material' import type { ReactElement } from 'react' import SafeLogo from '@/public/images/logo-no-text.svg' -import css from './styles.module.css' +import css from '@/components/new-safe/create/OverviewWidget/styles.module.css' const LOGO_DIMENSIONS = '22px' diff --git a/src/components/new-safe/OverviewWidget/styles.module.css b/src/components/new-safe/create/OverviewWidget/styles.module.css similarity index 100% rename from src/components/new-safe/OverviewWidget/styles.module.css rename to src/components/new-safe/create/OverviewWidget/styles.module.css diff --git a/src/components/new-safe/CreateSafe/__tests__/useSyncSafeCreationStep.test.ts b/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts similarity index 93% rename from src/components/new-safe/CreateSafe/__tests__/useSyncSafeCreationStep.test.ts rename to src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts index 7472e03927..cd8d2a011e 100644 --- a/src/components/new-safe/CreateSafe/__tests__/useSyncSafeCreationStep.test.ts +++ b/src/components/new-safe/create/__tests__/useSyncSafeCreationStep.test.ts @@ -1,5 +1,5 @@ import { renderHook } from '@/tests/test-utils' -import useSyncSafeCreationStep from '@/components/new-safe/CreateSafe/useSyncSafeCreationStep' +import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep' import * as wallet from '@/hooks/wallets/useWallet' import * as localStorage from '@/services/local-storage/useLocalStorage' import type { ConnectedWallet } from '@/services/onboard' diff --git a/src/components/new-safe/CreateSafe/index.tsx b/src/components/new-safe/create/index.tsx similarity index 89% rename from src/components/new-safe/CreateSafe/index.tsx rename to src/components/new-safe/create/index.tsx index 8f05af2d54..e554a3d51c 100644 --- a/src/components/new-safe/CreateSafe/index.tsx +++ b/src/components/new-safe/create/index.tsx @@ -2,21 +2,21 @@ import { Container, Typography, Grid } from '@mui/material' import { useRouter } from 'next/router' import useWallet from '@/hooks/wallets/useWallet' -import OverviewWidget from '../OverviewWidget' +import OverviewWidget from '@/components/new-safe/create/OverviewWidget' import type { NamedAddress } from '@/components/create-safe/types' -import type { TxStepperProps } from '../CardStepper/useCardStepper' -import CreateSafeStep0 from '@/components/new-safe/steps/Step0' -import CreateSafeStep1 from '@/components/new-safe/steps/Step1' -import CreateSafeStep2 from '@/components/new-safe/steps/Step2' -import CreateSafeStep3 from '@/components/new-safe/steps/Step3' -import { CreateSafeStatus } from '@/components/new-safe/steps/Step4' +import type { TxStepperProps } from '@/components/new-safe/CardStepper/useCardStepper' +import CreateSafeStep0 from '@/components/new-safe/create/steps/Step0' +import CreateSafeStep1 from '@/components/new-safe/create/steps/Step1' +import CreateSafeStep2 from '@/components/new-safe/create/steps/Step2' +import CreateSafeStep3 from '@/components/new-safe/create/steps/Step3' +import { CreateSafeStatus } from '@/components/new-safe/create/steps/Step4' import useAddressBook from '@/hooks/useAddressBook' -import { CardStepper } from '../CardStepper' +import { CardStepper } from '@/components/new-safe/CardStepper' import { AppRoutes } from '@/config/routes' import { CREATE_SAFE_CATEGORY } from '@/services/analytics' import type { AlertColor } from '@mui/material' -import type { CreateSafeInfoItem } from '../CreateSafeInfos' -import CreateSafeInfos from '../CreateSafeInfos' +import type { CreateSafeInfoItem } from '@/components/new-safe/create/CreateSafeInfos' +import CreateSafeInfos from '@/components/new-safe/create/CreateSafeInfos' import { type ReactElement, useMemo, useState } from 'react' import ExternalLink from '@/components/common/ExternalLink' diff --git a/src/components/new-safe/steps/Step0/index.tsx b/src/components/new-safe/create/steps/Step0/index.tsx similarity index 89% rename from src/components/new-safe/steps/Step0/index.tsx rename to src/components/new-safe/create/steps/Step0/index.tsx index ee2864f922..48e5c17291 100644 --- a/src/components/new-safe/steps/Step0/index.tsx +++ b/src/components/new-safe/create/steps/Step0/index.tsx @@ -4,12 +4,12 @@ import useWallet from '@/hooks/wallets/useWallet' import { useCurrentChain } from '@/hooks/useChains' import { isPairingSupported } from '@/services/pairing/utils' -import type { NewSafeFormData } from '@/components/new-safe/CreateSafe' +import type { NewSafeFormData } from '@/components/new-safe/create' import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' -import useSyncSafeCreationStep from '@/components/new-safe/CreateSafe/useSyncSafeCreationStep' -import layoutCss from '@/components/new-safe/CreateSafe/styles.module.css' +import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep' +import layoutCss from '@/components/new-safe/create/styles.module.css' import useLocalStorage from '@/services/local-storage/useLocalStorage' -import { type PendingSafeData, SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/steps/Step4' +import { type PendingSafeData, SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/create/steps/Step4' import useConnectWallet from '@/components/common/ConnectWallet/useConnectWallet' import KeyholeIcon from '@/components/common/icons/KeyholeIcon' import PairingDescription from '@/components/common/PairingDetails/PairingDescription' diff --git a/src/components/new-safe/steps/Step1/index.tsx b/src/components/new-safe/create/steps/Step1/index.tsx similarity index 88% rename from src/components/new-safe/steps/Step1/index.tsx rename to src/components/new-safe/create/steps/Step1/index.tsx index 0aae2b064f..543b208c7d 100644 --- a/src/components/new-safe/steps/Step1/index.tsx +++ b/src/components/new-safe/create/steps/Step1/index.tsx @@ -3,14 +3,14 @@ import { FormProvider, useForm } from 'react-hook-form' import { useMnemonicSafeName } from '@/hooks/useMnemonicName' import InfoIcon from '@/public/images/notifications/info.svg' import NetworkSelector from '@/components/common/NetworkSelector' -import type { StepRenderProps } from '../../CardStepper/useCardStepper' -import type { NewSafeFormData } from '../../CreateSafe' -import useSyncSafeCreationStep from '@/components/new-safe/CreateSafe/useSyncSafeCreationStep' +import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { NewSafeFormData } from '@/components/new-safe/create' +import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep' -import css from './styles.module.css' -import layoutCss from '@/components/new-safe/CreateSafe/styles.module.css' +import css from '@/components/new-safe/create/steps/Step1/styles.module.css' +import layoutCss from '@/components/new-safe/create/styles.module.css' import useIsWrongChain from '@/hooks/useIsWrongChain' -import NetworkWarning from '@/components/new-safe/NetworkWarning' +import NetworkWarning from '@/components/new-safe/create/NetworkWarning' import NameInput from '@/components/common/NameInput' import { CREATE_SAFE_EVENTS, trackEvent } from '@/services/analytics' import ExternalLink from '@/components/common/ExternalLink' diff --git a/src/components/new-safe/steps/Step1/styles.module.css b/src/components/new-safe/create/steps/Step1/styles.module.css similarity index 100% rename from src/components/new-safe/steps/Step1/styles.module.css rename to src/components/new-safe/create/steps/Step1/styles.module.css diff --git a/src/components/new-safe/steps/Step2/index.tsx b/src/components/new-safe/create/steps/Step2/index.tsx similarity index 89% rename from src/components/new-safe/steps/Step2/index.tsx rename to src/components/new-safe/create/steps/Step2/index.tsx index f03e83f39c..8461ed6d06 100644 --- a/src/components/new-safe/steps/Step2/index.tsx +++ b/src/components/new-safe/create/steps/Step2/index.tsx @@ -4,19 +4,19 @@ import type { ReactElement } from 'react' import AddIcon from '@/public/images/common/add.svg' import InfoIcon from '@/public/images/notifications/info.svg' -import { OwnerRow } from './OwnerRow' import type { NamedAddress } from '@/components/create-safe/types' -import type { StepRenderProps } from '../../CardStepper/useCardStepper' -import type { NewSafeFormData } from '../../CreateSafe' -import type { CreateSafeInfoItem } from '../../CreateSafeInfos' -import { useSafeSetupHints } from './useSafeSetupHints' -import useSyncSafeCreationStep from '@/components/new-safe/CreateSafe/useSyncSafeCreationStep' +import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { NewSafeFormData } from '@/components/new-safe/create' +import type { CreateSafeInfoItem } from '@/components/new-safe/create/CreateSafeInfos' +import { useSafeSetupHints } from '@/components/new-safe/create/steps/Step2/useSafeSetupHints' +import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep' import ArrowBackIcon from '@mui/icons-material/ArrowBack' -import css from './styles.module.css' -import layoutCss from '@/components/new-safe/CreateSafe/styles.module.css' -import NetworkWarning from '@/components/new-safe/NetworkWarning' +import css from '@/components/new-safe/create/steps/Step2/styles.module.css' +import layoutCss from '@/components/new-safe/create/styles.module.css' +import NetworkWarning from '@/components/new-safe/create/NetworkWarning' import useIsWrongChain from '@/hooks/useIsWrongChain' import { CREATE_SAFE_EVENTS, trackEvent } from '@/services/analytics' +import OwnerRow from '@/components/new-safe/OwnerRow' enum CreateSafeStep2Fields { owners = 'owners', diff --git a/src/components/new-safe/steps/Step2/styles.module.css b/src/components/new-safe/create/steps/Step2/styles.module.css similarity index 100% rename from src/components/new-safe/steps/Step2/styles.module.css rename to src/components/new-safe/create/steps/Step2/styles.module.css diff --git a/src/components/new-safe/steps/Step2/useSafeSetupHints.ts b/src/components/new-safe/create/steps/Step2/useSafeSetupHints.ts similarity index 92% rename from src/components/new-safe/steps/Step2/useSafeSetupHints.ts rename to src/components/new-safe/create/steps/Step2/useSafeSetupHints.ts index dad2b00e39..c8b7c793e6 100644 --- a/src/components/new-safe/steps/Step2/useSafeSetupHints.ts +++ b/src/components/new-safe/create/steps/Step2/useSafeSetupHints.ts @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import type { CreateSafeInfoItem } from '../../CreateSafeInfos' +import type { CreateSafeInfoItem } from '@/components/new-safe/create/CreateSafeInfos' export const useSafeSetupHints = ( threshold: number, diff --git a/src/components/new-safe/steps/Step3/index.tsx b/src/components/new-safe/create/steps/Step3/index.tsx similarity index 88% rename from src/components/new-safe/steps/Step3/index.tsx rename to src/components/new-safe/create/steps/Step3/index.tsx index 9f88a9e5b8..0b0fa354d0 100644 --- a/src/components/new-safe/steps/Step3/index.tsx +++ b/src/components/new-safe/create/steps/Step3/index.tsx @@ -1,4 +1,4 @@ -import { useMemo, type ReactElement } from 'react' +import { useMemo } from 'react' import { Button, Grid, Typography, Divider, Box } from '@mui/material' import ChainIndicator from '@/components/common/ChainIndicator' import EthHashInfo from '@/components/common/EthHashInfo' @@ -7,33 +7,21 @@ import useGasPrice from '@/hooks/useGasPrice' import { useEstimateSafeCreationGas } from '@/components/create-safe/useEstimateSafeCreationGas' import { formatVisualAmount } from '@/utils/formatters' import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' -import type { NewSafeFormData } from '@/components/new-safe/CreateSafe' -import css from './styles.module.css' -import layoutCss from '@/components/new-safe/CreateSafe/styles.module.css' +import type { NewSafeFormData } from '@/components/new-safe/create' +import css from '@/components/new-safe/create/steps/Step3/styles.module.css' +import layoutCss from '@/components/new-safe/create/styles.module.css' import { getFallbackHandlerContractInstance } from '@/services/contracts/safeContracts' import { computeNewSafeAddress } from '@/components/create-safe/logic' import useWallet from '@/hooks/wallets/useWallet' import { useWeb3 } from '@/hooks/wallets/web3' import useLocalStorage from '@/services/local-storage/useLocalStorage' -import { type PendingSafeData, SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/steps/Step4' -import useSyncSafeCreationStep from '@/components/new-safe/CreateSafe/useSyncSafeCreationStep' +import { type PendingSafeData, SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/create/steps/Step4' +import useSyncSafeCreationStep from '@/components/new-safe/create/useSyncSafeCreationStep' import ArrowBackIcon from '@mui/icons-material/ArrowBack' -import NetworkWarning from '@/components/new-safe/NetworkWarning' +import NetworkWarning from '@/components/new-safe/create/NetworkWarning' import useIsWrongChain from '@/hooks/useIsWrongChain' import palette from '@/styles/colors' - -const ReviewRow = ({ name, value }: { name: string; value: ReactElement }) => { - return ( - <> - - {name} - - - {value} - - - ) -} +import ReviewRow from '@/components/new-safe/ReviewRow' const CreateSafeStep3 = ({ data, onSubmit, onBack, setStep }: StepRenderProps) => { const isWrongChain = useIsWrongChain() diff --git a/src/components/new-safe/steps/Step3/styles.module.css b/src/components/new-safe/create/steps/Step3/styles.module.css similarity index 81% rename from src/components/new-safe/steps/Step3/styles.module.css rename to src/components/new-safe/create/steps/Step3/styles.module.css index 31a8389b86..e2b53bf895 100644 --- a/src/components/new-safe/steps/Step3/styles.module.css +++ b/src/components/new-safe/create/steps/Step3/styles.module.css @@ -2,4 +2,5 @@ display: flex; flex-direction: column; gap: var(--space-2); + font-size: 14px; } diff --git a/src/components/new-safe/steps/Step4/LoadingSpinner/index.tsx b/src/components/new-safe/create/steps/Step4/LoadingSpinner/index.tsx similarity index 93% rename from src/components/new-safe/steps/Step4/LoadingSpinner/index.tsx rename to src/components/new-safe/create/steps/Step4/LoadingSpinner/index.tsx index 2de0501100..f769059dc9 100644 --- a/src/components/new-safe/steps/Step4/LoadingSpinner/index.tsx +++ b/src/components/new-safe/create/steps/Step4/LoadingSpinner/index.tsx @@ -1,7 +1,7 @@ import { Box } from '@mui/material' -import css from './styles.module.css' +import css from '@/components/new-safe/create/steps/Step4/LoadingSpinner/styles.module.css' import classnames from 'classnames' -import { SafeCreationStatus } from '@/components/new-safe/steps/Step4/useSafeCreation' +import { SafeCreationStatus } from '@/components/new-safe/create/steps/Step4/useSafeCreation' import { useCallback, useEffect, useRef } from 'react' const rectTlEndTransform = 'translateX(0) translateY(20px) scaleY(1.1)' diff --git a/src/components/new-safe/steps/Step4/LoadingSpinner/styles.module.css b/src/components/new-safe/create/steps/Step4/LoadingSpinner/styles.module.css similarity index 100% rename from src/components/new-safe/steps/Step4/LoadingSpinner/styles.module.css rename to src/components/new-safe/create/steps/Step4/LoadingSpinner/styles.module.css diff --git a/src/components/new-safe/steps/Step4/StatusMessage.tsx b/src/components/new-safe/create/steps/Step4/StatusMessage.tsx similarity index 93% rename from src/components/new-safe/steps/Step4/StatusMessage.tsx rename to src/components/new-safe/create/steps/Step4/StatusMessage.tsx index fc70006930..161eff5666 100644 --- a/src/components/new-safe/steps/Step4/StatusMessage.tsx +++ b/src/components/new-safe/create/steps/Step4/StatusMessage.tsx @@ -1,6 +1,6 @@ import { Box, Typography } from '@mui/material' -import { SafeCreationStatus } from './useSafeCreation' -import LoadingSpinner from '@/components/new-safe/steps/Step4/LoadingSpinner' +import { SafeCreationStatus } from '@/components/new-safe/create/steps/Step4/useSafeCreation' +import LoadingSpinner from '@/components/new-safe/create/steps/Step4/LoadingSpinner' const getStep = (status: SafeCreationStatus) => { const ERROR_TEXT = 'Please cancel the process or retry the transaction.' diff --git a/src/components/new-safe/steps/Step4/StatusStep.tsx b/src/components/new-safe/create/steps/Step4/StatusStep.tsx similarity index 94% rename from src/components/new-safe/steps/Step4/StatusStep.tsx rename to src/components/new-safe/create/steps/Step4/StatusStep.tsx index 7d68272dd2..8a82eec354 100644 --- a/src/components/new-safe/steps/Step4/StatusStep.tsx +++ b/src/components/new-safe/create/steps/Step4/StatusStep.tsx @@ -1,6 +1,6 @@ import type { ReactNode } from 'react' import { Box, Skeleton, StepLabel, SvgIcon } from '@mui/material' -import css from '@/components/new-safe/steps/Step4/styles.module.css' +import css from '@/components/new-safe/create/steps/Step4/styles.module.css' import CircleIcon from '@mui/icons-material/Circle' import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined' import Identicon from '@/components/common/Identicon' diff --git a/src/components/new-safe/steps/Step4/StatusStepper.tsx b/src/components/new-safe/create/steps/Step4/StatusStepper.tsx similarity index 86% rename from src/components/new-safe/steps/Step4/StatusStepper.tsx rename to src/components/new-safe/create/steps/Step4/StatusStepper.tsx index 81f7b77ac3..9e76107bc0 100644 --- a/src/components/new-safe/steps/Step4/StatusStepper.tsx +++ b/src/components/new-safe/create/steps/Step4/StatusStepper.tsx @@ -1,9 +1,9 @@ import { Box, Step, StepConnector, Stepper, Typography } from '@mui/material' -import css from '@/components/new-safe/steps/Step4/styles.module.css' +import css from '@/components/new-safe/create/steps/Step4/styles.module.css' import EthHashInfo from '@/components/common/EthHashInfo' -import { SafeCreationStatus } from '@/components/new-safe/steps/Step4/useSafeCreation' -import type { PendingSafeData } from '@/components/new-safe/steps/Step4/index' -import StatusStep from '@/components/new-safe/steps/Step4/StatusStep' +import { SafeCreationStatus } from '@/components/new-safe/create/steps/Step4/useSafeCreation' +import type { PendingSafeData } from '@/components/new-safe/create/steps/Step4/index' +import StatusStep from '@/components/new-safe/create/steps/Step4/StatusStep' const StatusStepper = ({ pendingSafe, status }: { pendingSafe: PendingSafeData; status: SafeCreationStatus }) => { if (!pendingSafe?.safeAddress) return null diff --git a/src/components/new-safe/steps/Step4/index.tsx b/src/components/new-safe/create/steps/Step4/index.tsx similarity index 89% rename from src/components/new-safe/steps/Step4/index.tsx rename to src/components/new-safe/create/steps/Step4/index.tsx index aa826347ae..9a2dab05b6 100644 --- a/src/components/new-safe/steps/Step4/index.tsx +++ b/src/components/new-safe/create/steps/Step4/index.tsx @@ -5,19 +5,19 @@ import { useRouter } from 'next/router' import Track from '@/components/common/Track' import { CREATE_SAFE_EVENTS } from '@/services/analytics/events/createLoadSafe' import useLocalStorage from '@/services/local-storage/useLocalStorage' -import StatusMessage from '@/components/new-safe/steps/Step4/StatusMessage' +import StatusMessage from '@/components/new-safe/create/steps/Step4/StatusMessage' import useWallet from '@/hooks/wallets/useWallet' import useIsWrongChain from '@/hooks/useIsWrongChain' -import type { NewSafeFormData } from '@/components/new-safe/CreateSafe' +import type { NewSafeFormData } from '@/components/new-safe/create' import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' -import type { PendingSafeTx } from '@/components/create-safe/types.d' -import useSafeCreationEffects from '@/components/new-safe/steps/Step4/useSafeCreationEffects' -import { SafeCreationStatus, useSafeCreation } from '@/components/new-safe/steps/Step4/useSafeCreation' -import StatusStepper from '@/components/new-safe/steps/Step4/StatusStepper' +import type { PendingSafeTx } from '@/components/create-safe/types' +import useSafeCreationEffects from '@/components/new-safe/create/steps/Step4/useSafeCreationEffects' +import { SafeCreationStatus, useSafeCreation } from '@/components/new-safe/create/steps/Step4/useSafeCreation' +import StatusStepper from '@/components/new-safe/create/steps/Step4/StatusStepper' import { trackEvent } from '@/services/analytics' import useChainId from '@/hooks/useChainId' -import { getRedirect } from '@/components/new-safe/steps/Step4/logic' -import layoutCss from '@/components/new-safe/CreateSafe/styles.module.css' +import { getRedirect } from '@/components/new-safe/create/steps/Step4/logic' +import layoutCss from '@/components/new-safe/create/styles.module.css' import { AppRoutes } from '@/config/routes' import palette from '@/styles/colors' diff --git a/src/components/new-safe/steps/Step4/logic/index.ts b/src/components/new-safe/create/steps/Step4/logic/index.ts similarity index 96% rename from src/components/new-safe/steps/Step4/logic/index.ts rename to src/components/new-safe/create/steps/Step4/logic/index.ts index 442976b583..f34cc23bb0 100644 --- a/src/components/new-safe/steps/Step4/logic/index.ts +++ b/src/components/new-safe/create/steps/Step4/logic/index.ts @@ -3,13 +3,13 @@ import type { ChainInfo } from '@safe-global/safe-gateway-typescript-sdk' import { getProxyFactoryContractInstance } from '@/services/contracts/safeContracts' import type { ConnectedWallet } from '@/services/onboard' import { BigNumber } from '@ethersproject/bignumber' -import { SafeCreationStatus } from '@/components/new-safe/steps/Step4/useSafeCreation' +import { SafeCreationStatus } from '@/components/new-safe/create/steps/Step4/useSafeCreation' import { didRevert, type EthersError } from '@/utils/ethers-utils' import { Errors, logError } from '@/services/exceptions' import { ErrorCode } from '@ethersproject/logger' import { isWalletRejection } from '@/utils/wallets' import type { PendingSafeTx } from '@/components/create-safe/types' -import type { NewSafeFormData } from '@/components/new-safe/CreateSafe' +import type { NewSafeFormData } from '@/components/new-safe/create' import type { UrlObject } from 'url' import chains from '@/config/chains' import { AppRoutes } from '@/config/routes' diff --git a/src/components/new-safe/steps/Step4/styles.module.css b/src/components/new-safe/create/steps/Step4/styles.module.css similarity index 100% rename from src/components/new-safe/steps/Step4/styles.module.css rename to src/components/new-safe/create/steps/Step4/styles.module.css diff --git a/src/components/new-safe/steps/Step4/useSafeCreation.ts b/src/components/new-safe/create/steps/Step4/useSafeCreation.ts similarity index 96% rename from src/components/new-safe/steps/Step4/useSafeCreation.ts rename to src/components/new-safe/create/steps/Step4/useSafeCreation.ts index 009c3531f1..8d6eeab384 100644 --- a/src/components/new-safe/steps/Step4/useSafeCreation.ts +++ b/src/components/new-safe/create/steps/Step4/useSafeCreation.ts @@ -4,7 +4,7 @@ import { useWeb3, useWeb3ReadOnly } from '@/hooks/wallets/web3' import { useCurrentChain } from '@/hooks/useChains' import useWallet from '@/hooks/wallets/useWallet' import type { EthersError } from '@/utils/ethers-utils' -import type { PendingSafeData } from '@/components/new-safe/steps/Step4/index' +import type { PendingSafeData } from '@/components/new-safe/create/steps/Step4/index' import type { PendingSafeTx } from '@/components/create-safe/types' import { checkSafeCreationTx, @@ -12,7 +12,7 @@ import { handleSafeCreationError, SAFE_CREATION_ERROR_KEY, showSafeCreationError, -} from '@/components/new-safe/steps/Step4/logic' +} from '@/components/new-safe/create/steps/Step4/logic' import { useAppDispatch } from '@/store' import { closeByGroupKey } from '@/store/notificationsSlice' import { CREATE_SAFE_EVENTS, trackEvent } from '@/services/analytics' diff --git a/src/components/new-safe/steps/Step4/useSafeCreationEffects.ts b/src/components/new-safe/create/steps/Step4/useSafeCreationEffects.ts similarity index 92% rename from src/components/new-safe/steps/Step4/useSafeCreationEffects.ts rename to src/components/new-safe/create/steps/Step4/useSafeCreationEffects.ts index 50515a5721..91c32c03ed 100644 --- a/src/components/new-safe/steps/Step4/useSafeCreationEffects.ts +++ b/src/components/new-safe/create/steps/Step4/useSafeCreationEffects.ts @@ -1,12 +1,12 @@ import type { Dispatch, SetStateAction } from 'react' import { useEffect } from 'react' import { pollSafeInfo } from '@/components/create-safe/logic' -import { SafeCreationStatus } from '@/components/new-safe/steps/Step4/useSafeCreation' +import { SafeCreationStatus } from '@/components/new-safe/create/steps/Step4/useSafeCreation' import { CREATE_SAFE_EVENTS, trackEvent } from '@/services/analytics' import { updateAddressBook } from '@/components/create-safe/logic/address-book' import { useAppDispatch } from '@/store' import useChainId from '@/hooks/useChainId' -import type { PendingSafeData } from '@/components/new-safe/steps/Step4/index' +import type { PendingSafeData } from '@/components/new-safe/create/steps/Step4/index' const useSafeCreationEffects = ({ pendingSafe, diff --git a/src/components/new-safe/CreateSafe/styles.module.css b/src/components/new-safe/create/styles.module.css similarity index 100% rename from src/components/new-safe/CreateSafe/styles.module.css rename to src/components/new-safe/create/styles.module.css diff --git a/src/components/new-safe/CreateSafe/useSyncSafeCreationStep.ts b/src/components/new-safe/create/useSyncSafeCreationStep.ts similarity index 81% rename from src/components/new-safe/CreateSafe/useSyncSafeCreationStep.ts rename to src/components/new-safe/create/useSyncSafeCreationStep.ts index 0ada098a50..8e64022730 100644 --- a/src/components/new-safe/CreateSafe/useSyncSafeCreationStep.ts +++ b/src/components/new-safe/create/useSyncSafeCreationStep.ts @@ -1,9 +1,9 @@ import { useEffect } from 'react' import useLocalStorage from '@/services/local-storage/useLocalStorage' import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' -import type { PendingSafeData } from '@/components/new-safe/steps/Step4' -import type { NewSafeFormData } from '@/components/new-safe/CreateSafe/index' -import { SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/steps/Step4' +import type { PendingSafeData } from '@/components/new-safe/create/steps/Step4' +import type { NewSafeFormData } from '@/components/new-safe/create/index' +import { SAFE_PENDING_CREATION_STORAGE_KEY } from '@/components/new-safe/create/steps/Step4' import useWallet from '@/hooks/wallets/useWallet' const useSyncSafeCreationStep = (setStep: StepRenderProps['setStep']) => { diff --git a/src/components/new-safe/load/index.tsx b/src/components/new-safe/load/index.tsx new file mode 100644 index 0000000000..52ade6fd2d --- /dev/null +++ b/src/components/new-safe/load/index.tsx @@ -0,0 +1,75 @@ +import React from 'react' +import { useRouter } from 'next/router' + +import { LOAD_SAFE_CATEGORY } from '@/services/analytics' +import { Container, Grid, Typography } from '@mui/material' +import { CardStepper } from '@/components/new-safe/CardStepper' +import type { TxStepperProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { NamedAddress } from '@/components/create-safe/types' +import SetAddressStep from '@/components/new-safe/load/steps/SetAddressStep' +import { AppRoutes } from '@/config/routes' +import SafeOwnerStep from '@/components/new-safe/load/steps/SafeOwnerStep' +import SafeReviewStep from '@/components/new-safe/load/steps/SafeReviewStep' + +export type LoadSafeFormData = NamedAddress & { + threshold: number + owners: NamedAddress[] +} + +export const LoadSafeSteps: TxStepperProps['steps'] = [ + { + title: 'Connect wallet & select network', + subtitle: 'Select network on which the Safe was created', + render: (_, onSubmit, onBack, setStep) => ( + + ), + }, + { + title: 'Owners and confirmations', + subtitle: 'Optional: Provide a name for each owner.', + render: (data, onSubmit, onBack, setStep) => ( + + ), + }, + { + title: 'Review', + subtitle: 'Confirm loading Safe.', + render: (data, onSubmit, onBack, setStep) => ( + + ), + }, +] + +export const loadSafeDefaultData = { threshold: -1, owners: [], address: '', name: '' } + +const LoadSafe = ({ initialData }: { initialData?: TxStepperProps['initialData'] }) => { + const router = useRouter() + + const onClose = () => { + router.push(AppRoutes.welcome) + } + + const initialSafe = initialData ?? loadSafeDefaultData + + return ( + + + + + Load Safe + + + + + + + + ) +} + +export default LoadSafe diff --git a/src/components/new-safe/load/steps/SafeOwnerStep/index.tsx b/src/components/new-safe/load/steps/SafeOwnerStep/index.tsx new file mode 100644 index 0000000000..68ba7d1cbb --- /dev/null +++ b/src/components/new-safe/load/steps/SafeOwnerStep/index.tsx @@ -0,0 +1,91 @@ +import React, { useEffect } from 'react' +import { getSafeInfo, type SafeInfo } from '@safe-global/safe-gateway-typescript-sdk' +import { FormProvider, useFieldArray, useForm } from 'react-hook-form' +import { Box, Button, Divider } from '@mui/material' + +import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { LoadSafeFormData } from '@/components/new-safe/load' +import useAsync from '@/hooks/useAsync' +import useChainId from '@/hooks/useChainId' +import type { NamedAddress } from '@/components/create-safe/types' +import layoutCss from '@/components/new-safe/create/styles.module.css' +import ArrowBackIcon from '@mui/icons-material/ArrowBack' +import { OwnerRow } from '@/components/new-safe/OwnerRow' + +enum Field { + owners = 'owners', + threshold = 'threshold', +} + +type FormData = { + [Field.owners]: NamedAddress[] + [Field.threshold]: number +} + +const SafeOwnerStep = ({ data, onSubmit, onBack }: StepRenderProps) => { + const chainId = useChainId() + const formMethods = useForm({ + defaultValues: data, + mode: 'onChange', + }) + const { + handleSubmit, + setValue, + control, + formState: { isValid }, + getValues, + } = formMethods + + const { fields } = useFieldArray({ + control, + name: Field.owners, + }) + + const [safeInfo] = useAsync(() => { + if (data.address) { + return getSafeInfo(chainId, data.address) + } + }, [chainId, data.address]) + + useEffect(() => { + if (!safeInfo) return + + setValue(Field.threshold, safeInfo.threshold) + + const owners = safeInfo.owners.map((owner, i) => ({ + address: owner.value, + name: getValues(`owners.${i}.name`) || '', + })) + + setValue(Field.owners, owners) + }, [getValues, safeInfo, setValue]) + + const handleBack = () => { + onBack(getValues()) + } + + return ( + +
+ + {fields.map((field, index) => ( + + ))} + + + + + + + + + +
+ ) +} + +export default SafeOwnerStep diff --git a/src/components/new-safe/load/steps/SafeReviewStep/index.tsx b/src/components/new-safe/load/steps/SafeReviewStep/index.tsx new file mode 100644 index 0000000000..304d5ebe9a --- /dev/null +++ b/src/components/new-safe/load/steps/SafeReviewStep/index.tsx @@ -0,0 +1,142 @@ +import React from 'react' +import { Box, Button, Divider, Grid, Typography } from '@mui/material' + +import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { LoadSafeFormData } from '@/components/new-safe/load' +import layoutCss from '@/components/new-safe/create/styles.module.css' +import ArrowBackIcon from '@mui/icons-material/ArrowBack' +import ChainIndicator from '@/components/common/ChainIndicator' +import css from '@/components/new-safe/create/steps/Step3/styles.module.css' +import EthHashInfo from '@/components/common/EthHashInfo' +import { useCurrentChain } from '@/hooks/useChains' +import { useAppDispatch } from '@/store' +import { useRouter } from 'next/router' +import { addOrUpdateSafe } from '@/store/addedSafesSlice' +import { defaultSafeInfo } from '@/store/safeInfoSlice' +import { upsertAddressBookEntry } from '@/store/addressBookSlice' +import { LOAD_SAFE_EVENTS, trackEvent } from '@/services/analytics' +import { AppRoutes } from '@/config/routes' +import ReviewRow from '@/components/new-safe/ReviewRow' + +const SafeReviewStep = ({ data, onBack }: StepRenderProps) => { + const chain = useCurrentChain() + const dispatch = useAppDispatch() + const router = useRouter() + const chainId = chain?.chainId || '' + + const addSafe = () => { + const safeName = data.name + const safeAddress = data.address + + dispatch( + addOrUpdateSafe({ + safe: { + ...defaultSafeInfo, + address: { value: safeAddress, name: safeName }, + threshold: data.threshold, + owners: data.owners.map((owner) => ({ + value: owner.address, + name: owner.name || owner.ens, + })), + chainId, + }, + }), + ) + + dispatch( + upsertAddressBookEntry({ + chainId, + address: safeAddress, + name: safeName, + }), + ) + + for (const { address, name, ens } of data.owners) { + const entryName = name || ens + + if (!entryName) { + continue + } + + dispatch( + upsertAddressBookEntry({ + chainId, + address, + name: entryName, + }), + ) + } + + trackEvent({ + ...LOAD_SAFE_EVENTS.OWNERS, + label: data.owners.length, + }) + + trackEvent({ + ...LOAD_SAFE_EVENTS.THRESHOLD, + label: data.threshold, + }) + + trackEvent(LOAD_SAFE_EVENTS.GO_TO_SAFE) + + router.push({ + pathname: AppRoutes.home, + query: { safe: `${chain?.shortName}:${safeAddress}` }, + }) + } + + const handleBack = () => { + onBack(data) + } + + return ( + <> + + + } /> + {data.name}} /> + + {data.owners.map((owner, index) => ( + + ))} + + } + /> + + {data.threshold} out of {data.owners.length} owner(s) + + } + /> +
+ + + + + + + + + + ) +} + +export default SafeReviewStep diff --git a/src/components/new-safe/load/steps/SetAddressStep/index.tsx b/src/components/new-safe/load/steps/SetAddressStep/index.tsx new file mode 100644 index 0000000000..85952f8333 --- /dev/null +++ b/src/components/new-safe/load/steps/SetAddressStep/index.tsx @@ -0,0 +1,161 @@ +import type { StepRenderProps } from '@/components/new-safe/CardStepper/useCardStepper' +import type { LoadSafeFormData } from '@/components/new-safe/load' +import { FormProvider, useForm } from 'react-hook-form' +import { + Box, + Button, + CircularProgress, + Divider, + Grid, + InputAdornment, + SvgIcon, + Tooltip, + Typography, +} from '@mui/material' +import layoutCss from '@/components/new-safe/create/styles.module.css' +import NameInput from '@/components/common/NameInput' +import InfoIcon from '@/public/images/notifications/info.svg' +import css from '@/components/new-safe/create/steps/Step1/styles.module.css' +import NetworkSelector from '@/components/common/NetworkSelector' +import { useMnemonicSafeName } from '@/hooks/useMnemonicName' +import { useAddressResolver } from '@/hooks/useAddressResolver' +import ArrowBackIcon from '@mui/icons-material/ArrowBack' +import AddressInput from '@/components/common/AddressInput' +import React from 'react' +import { getSafeInfo } from '@safe-global/safe-gateway-typescript-sdk' +import useChainId from '@/hooks/useChainId' +import { useAppSelector } from '@/store' +import { selectAddedSafes } from '@/store/addedSafesSlice' +import { LOAD_SAFE_EVENTS, trackEvent } from '@/services/analytics' +import ExternalLink from '@/components/common/ExternalLink' + +enum Field { + name = 'name', + address = 'address', +} + +type FormData = { + [Field.name]: string + [Field.address]: string +} + +const SetAddressStep = ({ data, onSubmit, onBack }: StepRenderProps) => { + const currentChainId = useChainId() + const addedSafes = useAppSelector((state) => selectAddedSafes(state, currentChainId)) + const formMethods = useForm({ + mode: 'all', + defaultValues: { + [Field.name]: data.name, + [Field.address]: data.address, + }, + }) + + const { + handleSubmit, + formState: { errors, isValid }, + watch, + getValues, + } = formMethods + + const safeAddress = watch(Field.address) + + const randomName = useMnemonicSafeName() + const { ens, name, resolving } = useAddressResolver(safeAddress) + + // Address book, ENS, mnemonic + const fallbackName = name || ens || randomName + + const validateSafeAddress = async (address: string) => { + if (addedSafes && Object.keys(addedSafes).includes(address)) { + return 'Safe is already added' + } + + try { + await getSafeInfo(currentChainId, address) + } catch (error) { + return 'Address given is not a valid Safe address' + } + } + + const onFormSubmit = handleSubmit((data: FormData) => { + onSubmit({ + ...data, + [Field.name]: data[Field.name] || fallbackName, + }) + + if (data[Field.name]) { + trackEvent(LOAD_SAFE_EVENTS.NAME_SAFE) + } + }) + + const handleBack = () => { + const formData = getValues() + onBack({ + ...formData, + [Field.name]: formData.name || fallbackName, + }) + } + + return ( + +
+ + + + + + + ) : ( + + + + + + ), + }} + /> + + + + + + + + + + + + By continuing you consent to the terms of use{' '} + and privacy policy. + + + + + + + + + + + + +
+ ) +} + +export default SetAddressStep diff --git a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx index 1808aced5b..81c88dda63 100644 --- a/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx +++ b/src/components/safe-apps/SafeAppLandingPage/AppActions.tsx @@ -71,7 +71,7 @@ const AppActions = ({ wallet, onConnectWallet, chain, appUrl, app }: Props): Rea case shouldCreateSafe: const redirect = `${AppRoutes.apps}?appUrl=${appUrl}` const createSafeHrefWithRedirect: UrlObject = { - pathname: AppRoutes.open, + pathname: AppRoutes.newSafe.create, query: { safeViewRedirectURL: redirect, chain: chain.shortName }, } button = ( diff --git a/src/components/sidebar/SafeListItem/index.tsx b/src/components/sidebar/SafeListItem/index.tsx index 0a932a9189..a87aea66b0 100644 --- a/src/components/sidebar/SafeListItem/index.tsx +++ b/src/components/sidebar/SafeListItem/index.tsx @@ -64,7 +64,7 @@ const SafeListItem = ({ address={address} onClick={closeDrawer} href={{ - pathname: AppRoutes.load, + pathname: AppRoutes.newSafe.load, query: { chain: shortName, address }, }} /> diff --git a/src/components/welcome/index.tsx b/src/components/welcome/index.tsx index 99160f9e6c..01c8732874 100644 --- a/src/components/welcome/index.tsx +++ b/src/components/welcome/index.tsx @@ -29,7 +29,7 @@ const NewSafe = () => { for creating your new Safe. - @@ -44,7 +44,7 @@ const NewSafe = () => { address. - diff --git a/src/config/routes.ts b/src/config/routes.ts index 2e82d827c8..6fd0bfd99d 100644 --- a/src/config/routes.ts +++ b/src/config/routes.ts @@ -1,8 +1,6 @@ export const AppRoutes = { '404': '/404', welcome: '/welcome', - open: '/open', - load: '/load', index: '/', import: '/import', home: '/home', @@ -14,6 +12,7 @@ export const AppRoutes = { }, newSafe: { create: '/new-safe/create', + load: '/new-safe/load', }, settings: { spendingLimits: '/settings/spending-limits', diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 2a52c4c6fa..ac98f28c18 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,16 +1,18 @@ -import { useLayoutEffect } from 'react' +import { useEffect, useLayoutEffect } from 'react' import type { NextPage } from 'next' import { useRouter } from 'next/router' import useLastSafe from '@/hooks/useLastSafe' import { AppRoutes } from '@/config/routes' +const useIsomorphicEffect = typeof window !== 'undefined' ? useLayoutEffect : useEffect + const IndexPage: NextPage = () => { const router = useRouter() const { safe, chain } = router.query const lastSafe = useLastSafe() const safeAddress = safe || lastSafe - useLayoutEffect(() => { + useIsomorphicEffect(() => { router.replace( safeAddress ? `${AppRoutes.home}?safe=${safeAddress}` diff --git a/src/pages/new-safe/create.tsx b/src/pages/new-safe/create.tsx index e158c36424..5327d3c5b8 100644 --- a/src/pages/new-safe/create.tsx +++ b/src/pages/new-safe/create.tsx @@ -1,7 +1,7 @@ import Head from 'next/head' import type { NextPage } from 'next' -import CreateSafe from '@/components/new-safe/CreateSafe' +import CreateSafe from '@/components/new-safe/create' const Open: NextPage = () => { return ( diff --git a/src/pages/load.tsx b/src/pages/new-safe/load.tsx similarity index 65% rename from src/pages/load.tsx rename to src/pages/new-safe/load.tsx index 4cd8af13dd..882f2950a0 100644 --- a/src/pages/load.tsx +++ b/src/pages/new-safe/load.tsx @@ -1,7 +1,7 @@ import type { NextPage } from 'next' import Head from 'next/head' import { useRouter } from 'next/router' -import LoadSafe from '@/components/load-safe' +import LoadSafe, { loadSafeDefaultData } from '@/components/new-safe/load' const Load: NextPage = () => { const router = useRouter() @@ -15,9 +15,9 @@ const Load: NextPage = () => { {safeAddress ? ( - + ) : ( - + )} ) diff --git a/src/pages/open.tsx b/src/pages/open.tsx deleted file mode 100644 index 5ca00b96ec..0000000000 --- a/src/pages/open.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import type { NextPage } from 'next' -import Head from 'next/head' -import CreateSafe from '@/components/create-safe' -import { useRouter } from 'next/router' -import useABTesting from '@/services/tracking/useABTesting' -import { AbTest } from '@/services/tracking/abTesting' -import { useLayoutEffect } from 'react' -import { AppRoutes } from '@/config/routes' - -const Open: NextPage = () => { - const shouldUseNewCreation = useABTesting(AbTest.SAFE_CREATION) - const router = useRouter() - - useLayoutEffect(() => { - if (shouldUseNewCreation) { - router.replace(AppRoutes.newSafe.create) - } - }, [router, shouldUseNewCreation]) - - if (shouldUseNewCreation) { - return <> - } - - return ( -
- - Safe – Create Safe - - - -
- ) -} - -export default Open