From c319185119556fe8b6799bb2290fca19f7457d2f Mon Sep 17 00:00:00 2001 From: Dmitry <61683409+0xDmitry@users.noreply.github.com> Date: Sat, 11 Nov 2023 02:01:02 +0300 Subject: [PATCH] feat(website): queue transactions improve ux (#606) --- packages/website/src/app/deploy/layout.tsx | 8 +- .../components/EditableAutocompleteInput.tsx | 30 ++--- .../features/Deploy/QueueTransactionsPage.tsx | 117 ++++++++++++------ .../src/features/Deploy/SafeAddressInput.tsx | 1 + packages/website/src/hooks/cannon.ts | 6 +- 5 files changed, 98 insertions(+), 64 deletions(-) diff --git a/packages/website/src/app/deploy/layout.tsx b/packages/website/src/app/deploy/layout.tsx index 277644303..2c75e9045 100644 --- a/packages/website/src/app/deploy/layout.tsx +++ b/packages/website/src/app/deploy/layout.tsx @@ -1,11 +1,15 @@ 'use client'; +import dynamic from 'next/dynamic'; import { ReactNode } from 'react'; import { Box, Flex, useBreakpointValue } from '@chakra-ui/react'; import { usePathname } from 'next/navigation'; import { links } from '@/constants/links'; import { NavLink } from '@/components/NavLink'; -import WithSafe from '@/features/Deploy/WithSafe'; + +const NoSSRWithSafe = dynamic(() => import('@/features/Deploy/WithSafe'), { + ssr: false, +}); export default function DeployLayout({ children }: { children: ReactNode }) { const pathname = usePathname(); @@ -47,7 +51,7 @@ export default function DeployLayout({ children }: { children: ReactNode }) { - {children} + {children} ); } diff --git a/packages/website/src/components/EditableAutocompleteInput.tsx b/packages/website/src/components/EditableAutocompleteInput.tsx index 0e3ac01fa..632c3c344 100644 --- a/packages/website/src/components/EditableAutocompleteInput.tsx +++ b/packages/website/src/components/EditableAutocompleteInput.tsx @@ -109,7 +109,6 @@ export function EditableAutocompleteInput(props: { const editableInputRef = useRef(); function tabToNext() { - console.log('tabdata trigger tabtonext'); const tabElements = Array.from( document // Get all elements that can be focusable @@ -144,10 +143,6 @@ export function EditableAutocompleteInput(props: { (e: any) => e === editableInputRef.current ); - //console.log('tabdata current index', currentIndex); - //console.log('tabdata', editableInputRef) - //console.log('tabdata', tabElements); - const nextIndex = (currentIndex + 1) % tabElements.length; tabElements[nextIndex].focus(); } @@ -201,7 +196,6 @@ export function EditableAutocompleteInput(props: { onEdit={() => setIsEditing(true)} onBlur={() => { finishEdit(); - tabToNext(); }} onKeyDown={handleKey} onChange={(value) => { @@ -243,15 +237,14 @@ export function EditableAutocompleteInput(props: { key={index} item={item} filterInput={filterInput} - selected={item.label === pendingItem} - isVisible={isEditing && filteredItems.length > 0} onMouseOver={() => setPendingItem(item.label)} + onMouseDown={(evt: any) => { + evt.preventDefault(); + }} onClick={() => { - console.log('tabdata click'); setPendingItem(item.label); setFilterInput(item.label); tabToNext(); - console.log('tabdata end'); }} internalRef={ (item.label === pendingItem @@ -271,10 +264,9 @@ export function EditableAutocompleteInput(props: { function AutocompleteOption(props: { item: { label: string; secondary: string }; filterInput: string; - selected?: boolean; onMouseOver: () => void; + onMouseDown: (evt: any) => void; onClick: () => void; - isVisible: boolean; // eslint-disable-next-line no-undef internalRef: React.MutableRefObject | undefined; }) { @@ -287,21 +279,13 @@ function AutocompleteOption(props: { { - evt.preventDefault(); - props.onClick(); - }} + onMouseDown={(evt: any) => props.onMouseDown(evt)} + onClick={props.onClick} background="black" px="2" pb="1" > - { - evt.preventDefault(); - props.onClick(); - }} - gap={0} - > + {matched.map((p, i) => [ {p}, i < matched.length - 1 ? {props.filterInput} : [], diff --git a/packages/website/src/features/Deploy/QueueTransactionsPage.tsx b/packages/website/src/features/Deploy/QueueTransactionsPage.tsx index fb13277b3..c93ed0699 100644 --- a/packages/website/src/features/Deploy/QueueTransactionsPage.tsx +++ b/packages/website/src/features/Deploy/QueueTransactionsPage.tsx @@ -1,6 +1,6 @@ 'use client'; -import { AddIcon, MinusIcon } from '@chakra-ui/icons'; +import { AddIcon } from '@chakra-ui/icons'; import { Alert, AlertDescription, @@ -18,9 +18,11 @@ import { Tooltip, useToast, Text, + IconButton, + Flex, } from '@chakra-ui/react'; -import _ from 'lodash'; -import { useState } from 'react'; +import { CloseIcon } from '@chakra-ui/icons'; +import { useEffect, useState } from 'react'; import { useRouter } from 'next/navigation'; import { Abi, @@ -42,6 +44,11 @@ import { makeMultisend } from '@/helpers/multisend'; import { DisplayedTransaction } from './DisplayedTransaction'; import NoncePicker from './NoncePicker'; +type IdentifiableTxn = { + txn: Omit; + id: string; +}; + export default function QueueTransactionsPage() { return ; } @@ -51,9 +58,11 @@ function QueueTransactions() { const router = useRouter(); const [target, setTarget] = useState(''); - const [queuedTxns, setQueuedTxns] = useState< - Omit[] - >([null as any]); + + const [lastQueuedTxnsId, setLastQueuedTxnsId] = useState(0); + const [queuedIdentifiableTxns, setQueuedIdentifiableTxns] = useState< + IdentifiableTxn[] + >([{ txn: null as any, id: String(lastQueuedTxnsId) }]); const [pickedNonce, setPickedNonce] = useState(null); @@ -63,6 +72,8 @@ function QueueTransactions() { `${currentSafe?.chainId}-${settings.preset}` ); + const queuedTxns = queuedIdentifiableTxns.map((item) => item.txn); + const multisendTxn = queuedTxns.indexOf(null as any) === -1 ? makeMultisend( @@ -127,10 +138,29 @@ function QueueTransactions() { i: number, txn: Omit ) { - queuedTxns[i] = txn; - setQueuedTxns(_.clone(queuedTxns)); + setQueuedIdentifiableTxns((prev) => { + const result = [...prev]; + result[i].txn = txn; + return result; + }); } + const removeQueuedTxn = (i: number) => { + setQueuedIdentifiableTxns((prev) => { + const result = [...prev]; + result.splice(i, 1); + return result; + }); + }; + + const addQueuedTxn = () => { + setQueuedIdentifiableTxns((prev) => [ + ...prev, + { txn: {}, id: String(lastQueuedTxnsId + 1) }, + ]); + setLastQueuedTxnsId((prev) => prev + 1); + }; + const txnHasError = !!txnInfo.txnResults.filter((r) => r?.error).length; console.log('TXN HAS ERROR', txnHasError); @@ -158,6 +188,17 @@ function QueueTransactions() { const disableExecute = !multisendTxn || txnHasError || !!stager.execConditionFailed; + console.log('xxx cannonInfo: ', cannonInfo); + + useEffect(() => { + if (!cannonInfo.contracts) { + setQueuedIdentifiableTxns([ + { txn: null as any, id: String(lastQueuedTxnsId + 1) }, + ]); + setLastQueuedTxnsId((prev) => prev + 1); + } + }, [cannonInfo.contracts]); + return ( @@ -182,7 +223,7 @@ function QueueTransactions() { connected wallet. - {cannonInfo.pkgUrl && !cannonInfo.contracts && ( + {!isAddress(target) && cannonInfo.pkgUrl && !cannonInfo.contracts && ( @@ -193,18 +234,32 @@ function QueueTransactions() { )} - {cannonInfo.contracts && ( + {!isAddress(target) && cannonInfo.contracts && ( Transactions - {queuedTxns.map((_, i) => ( - - updateQueuedTxn(i, txn as any)} - /> + {queuedIdentifiableTxns.map((queuedIdentifiableTxn, i) => ( + + + updateQueuedTxn(i, txn as any)} + /> + {queuedIdentifiableTxns.length > 1 && ( + + } + aria-label={'Remove provider'} + onClick={() => removeQueuedTxn(i)} + /> + + )} + {txnInfo.txnResults && - txnInfo.txnResults.length === queuedTxns.length && + txnInfo.txnResults.length === queuedIdentifiableTxns.length && txnInfo.txnResults[i] && txnInfo.txnResults[i]?.error && ( @@ -215,7 +270,7 @@ function QueueTransactions() { : txnInfo.txnResults[i]?.error} )} - + ))} - {queuedTxns.length > 1 && ( - - )} )} @@ -256,6 +293,8 @@ function QueueTransactions() { Value updateQueuedTxn(0, { ...queuedTxns[0], @@ -273,6 +312,8 @@ function QueueTransactions() { Transaction Data updateQueuedTxn(0, { diff --git a/packages/website/src/features/Deploy/SafeAddressInput.tsx b/packages/website/src/features/Deploy/SafeAddressInput.tsx index ae3229d43..2c2366a91 100644 --- a/packages/website/src/features/Deploy/SafeAddressInput.tsx +++ b/packages/website/src/features/Deploy/SafeAddressInput.tsx @@ -165,6 +165,7 @@ export function SafeAddressInput() { <>