diff --git a/extension/src/chains/const.ts b/extension/src/chains/const.ts index be96c3a7..e4b2f027 100644 --- a/extension/src/chains/const.ts +++ b/extension/src/chains/const.ts @@ -2,6 +2,8 @@ import { ChainId } from 'ser-kit' +export const ETH_ZERO_ADDRESS = 'eth:0x0000000000000000000000000000000000000000' + export const RPC: Record = { 1: 'https://airlock.gnosisguild.org/api/v1/1/rpc', 10: 'https://airlock.gnosisguild.org/api/v1/10/rpc', diff --git a/extension/src/components/Button/index.tsx b/extension/src/components/Button/index.tsx index 3464389c..c3354d05 100644 --- a/extension/src/components/Button/index.tsx +++ b/extension/src/components/Button/index.tsx @@ -4,13 +4,20 @@ import classes from './style.module.css' type ButtonProps = ComponentPropsWithoutRef<'button'> & { secondary?: boolean + fluid?: boolean } -const Button = ({ className, secondary = false, ...rest }: ButtonProps) => ( +const Button = ({ + className, + secondary = false, + fluid = false, + ...rest +}: ButtonProps) => ( - - - + + + - - + + @@ -218,13 +140,13 @@ export const EditRoute = () => { updateConnection({ chainId })} /> { updateConnection({ providerType, @@ -243,8 +165,7 @@ export const EditRoute = () => { value={avatarAddress === ZeroAddress ? '' : avatarAddress || ''} onChange={async (address) => { const keepTransactionBundle = - address.toLowerCase() === - connection.avatarAddress.toLowerCase() + address.toLowerCase() === avatarAddress.toLowerCase() const confirmed = keepTransactionBundle || (await confirmClearTransactions()) @@ -291,17 +212,14 @@ export const EditRoute = () => { if (mod?.type === KnownContracts.ROLES_V1) { updateConnection({ multisend: await queryRolesV1MultiSend( - connection.chainId, + chainId, mod.moduleAddress ), }) } if (mod?.type === KnownContracts.ROLES_V2) { updateConnection( - await queryRolesV2MultiSend( - connection.chainId, - mod.moduleAddress - ) + await queryRolesV2MultiSend(chainId, mod.moduleAddress) ) } }} @@ -325,7 +243,7 @@ export const EditRoute = () => { { + onChange={(ev) => { updateConnection({ roleId: ev.target.value }) }} placeholder="0" @@ -336,7 +254,7 @@ export const EditRoute = () => { { try { diff --git a/extension/src/panel/app-routes/edit-route/LaunchButton.tsx b/extension/src/panel/app-routes/edit-route/LaunchButton.tsx new file mode 100644 index 00000000..3ecdc7c7 --- /dev/null +++ b/extension/src/panel/app-routes/edit-route/LaunchButton.tsx @@ -0,0 +1,60 @@ +import { Button } from '@/components' +import { ZodiacRoute } from '@/types' +import { + useSaveZodiacRoute, + useSelectedRouteId, + useZodiacRoute, +} from '@/zodiac-routes' +import { useNavigate } from 'react-router-dom' + +type LaunchButtonProps = { + disabled: boolean + + initialRouteState: ZodiacRoute + currentRouteState: ZodiacRoute + + onNeedConfirmationToClearTransactions: () => Promise +} + +export const LaunchButton = ({ + disabled, + initialRouteState, + currentRouteState, + + onNeedConfirmationToClearTransactions, +}: LaunchButtonProps) => { + const [, setSelectedRouteId] = useSelectedRouteId() + const currentZodiacRoute = useZodiacRoute() + const saveRoute = useSaveZodiacRoute() + + const navigate = useNavigate() + + return ( + + ) +} diff --git a/extension/src/panel/app-routes/edit-route/RemoveButton.tsx b/extension/src/panel/app-routes/edit-route/RemoveButton.tsx new file mode 100644 index 00000000..4c3ae714 --- /dev/null +++ b/extension/src/panel/app-routes/edit-route/RemoveButton.tsx @@ -0,0 +1,24 @@ +import { IconButton } from '@/components' +import { useRemoveZodiacRoute } from '@/zodiac-routes' +import { RiDeleteBinLine } from 'react-icons/ri' +import { useNavigate } from 'react-router-dom' +import { useRouteId } from './useRouteId' + +export const RemoveButton = () => { + const navigate = useNavigate() + const removeRouteById = useRemoveZodiacRoute() + const routeId = useRouteId() + + return ( + { + removeRouteById(routeId) + navigate('/routes') + }} + className="aspect-square border-[3px] border-double border-red-800 p-1 hover:bg-zodiac-light-red hover:bg-opacity-20" + > + + + ) +} diff --git a/extension/src/panel/app-routes/edit-route/style.module.css b/extension/src/panel/app-routes/edit-route/style.module.css index 2f9c2f69..add6f687 100644 --- a/extension/src/panel/app-routes/edit-route/style.module.css +++ b/extension/src/panel/app-routes/edit-route/style.module.css @@ -1,36 +1,3 @@ -.editContainer { - position: relative; -} -h2 { - font-size: 1.5em; - margin: 0em; -} - -.launchButton { - width: auto; - padding: 4px 24px; -} - -button.removeButton { - height: auto; - width: auto; - aspect-ratio: 1/1; - padding: 4px; - border: 3px double crimson; -} - -button.removeButton:hover { - background-color: rgba(220, 20, 60, 0.2); -} - -.backLink { - position: absolute; - top: -25px; - left: 0; - font-size: 0.8em; - text-decoration: none; -} - .form { flex-grow: 1; } diff --git a/extension/src/panel/app-routes/edit-route/useConnectionDryRun.tsx b/extension/src/panel/app-routes/edit-route/useConnectionDryRun.tsx index 682e985e..b40f54ff 100644 --- a/extension/src/panel/app-routes/edit-route/useConnectionDryRun.tsx +++ b/extension/src/panel/app-routes/edit-route/useConnectionDryRun.tsx @@ -1,7 +1,7 @@ import { getReadOnlyProvider } from '@/providers' import { JsonRpcError, LegacyConnection } from '@/types' import { isSmartContractAddress, validateAddress } from '@/utils' -import { useRoute } from '@/zodiac-routes' +import { useZodiacRoute } from '@/zodiac-routes' import { KnownContracts } from '@gnosis.pm/zodiac' import { useEffect, useState } from 'react' import { @@ -14,7 +14,7 @@ import { wrapRequest } from './wrapRequest' export const useConnectionDryRun = (connection: LegacyConnection) => { const [error, setError] = useState(null) - const { connected } = useRoute(connection.id) + const { connected } = useZodiacRoute(connection.id) useEffect(() => { const { pilotAddress, avatarAddress, moduleAddress, moduleType, roleId } = diff --git a/extension/src/panel/app-routes/edit-route/useRouteId.ts b/extension/src/panel/app-routes/edit-route/useRouteId.ts new file mode 100644 index 00000000..ea367bf5 --- /dev/null +++ b/extension/src/panel/app-routes/edit-route/useRouteId.ts @@ -0,0 +1,10 @@ +import { invariant } from '@epic-web/invariant' +import { useParams } from 'react-router-dom' + +export const useRouteId = () => { + const { routeId } = useParams() + + invariant(routeId != null, 'No "routeId" found in params') + + return routeId +} diff --git a/extension/src/panel/app-routes/edit-route/wallet/ConnectWallet.tsx b/extension/src/panel/app-routes/edit-route/wallet/ConnectWallet.tsx index da190cd0..d299f7b1 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/ConnectWallet.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/ConnectWallet.tsx @@ -4,14 +4,14 @@ import { WalletConnectResult, isConnected as isConnectedBase, } from '@/providers' -import { ProviderType, Route } from '@/types' +import { ProviderType, ZodiacRoute } from '@/types' import { ZeroAddress } from 'ethers' import { ChainId, parsePrefixedAddress } from 'ser-kit' import { InjectedWallet, InjectedWalletConnect } from './injectedWallet' import { WalletConnect, WalletConnectConnect } from './walletConnect' interface Props { - route: Route + route: ZodiacRoute onConnect(args: { providerType: ProviderType chainId: ChainId @@ -80,7 +80,7 @@ export const ConnectWallet = ({ route, onConnect, onDisconnect }: Props) => { } } -const getPilotAddress = (route: Route) => { +const getPilotAddress = (route: ZodiacRoute) => { if (route.initiator == null) { return null } diff --git a/extension/src/panel/app-routes/edit-route/wallet/Connected.tsx b/extension/src/panel/app-routes/edit-route/wallet/Connected.tsx index 1a844028..5141a460 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/Connected.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/Connected.tsx @@ -10,6 +10,8 @@ export const Connected = ({ children, onDisconnect }: ConnectedProps) => (
{children} - +
) diff --git a/extension/src/panel/app-routes/edit-route/wallet/SwitchChain.tsx b/extension/src/panel/app-routes/edit-route/wallet/SwitchChain.tsx index 80aa8ed1..4607536e 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/SwitchChain.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/SwitchChain.tsx @@ -30,10 +30,16 @@ export const SwitchChain = ({ {onSwitch && ( - + )} - {onDisconnect && } + {onDisconnect && ( + + )} ) diff --git a/extension/src/panel/app-routes/edit-route/wallet/WalletDisconnected.tsx b/extension/src/panel/app-routes/edit-route/wallet/WalletDisconnected.tsx index c7cd5cba..7f95b9fc 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/WalletDisconnected.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/WalletDisconnected.tsx @@ -21,8 +21,12 @@ export const WalletDisconnected = ({ {children} - - + + ) diff --git a/extension/src/panel/app-routes/edit-route/wallet/WrongAccount.tsx b/extension/src/panel/app-routes/edit-route/wallet/WrongAccount.tsx index 62284643..ef8278a6 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/WrongAccount.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/WrongAccount.tsx @@ -14,6 +14,8 @@ export const WrongAccount = ({ children, onDisconnect }: WrongAccountProps) => ( {children} - + ) diff --git a/extension/src/panel/app-routes/edit-route/wallet/injectedWallet/InjectedWalletConnect.tsx b/extension/src/panel/app-routes/edit-route/wallet/injectedWallet/InjectedWalletConnect.tsx index 5530922c..a1cd801f 100644 --- a/extension/src/panel/app-routes/edit-route/wallet/injectedWallet/InjectedWalletConnect.tsx +++ b/extension/src/panel/app-routes/edit-route/wallet/injectedWallet/InjectedWalletConnect.tsx @@ -17,6 +17,7 @@ export const InjectedWalletConnect = ({ onConnect }: InjectedWalletProps) => { return ( - - +
+

Pilot Routes

+ + +
{routes.map((route) => ( - { + selectRoute(routeId) + + navigate('/') + }} + onModify={(routeId) => { + navigate('/routes/' + routeId) + }} /> ))} diff --git a/extension/src/panel/app-routes/list-routes/Route.tsx b/extension/src/panel/app-routes/list-routes/Route.tsx new file mode 100644 index 00000000..077a75ea --- /dev/null +++ b/extension/src/panel/app-routes/list-routes/Route.tsx @@ -0,0 +1,112 @@ +import { Box, BoxButton, ConnectionStack, Flex } from '@/components' +import { ZodiacRoute } from '@/types' +import { useZodiacRoute } from '@/zodiac-routes' +import { formatDistanceToNow } from 'date-fns' +import { asLegacyConnection } from '../legacyConnectionMigrations' +import { useConfirmClearTransactions } from '../useConfirmClearTransaction' +import { ConnectedIcon } from './ConnectedIcon' +import { DisconnectedIcon } from './DisconnectedIcon' + +interface RouteProps { + route: ZodiacRoute + onLaunch: (routeId: string) => void + onModify: (routeId: string) => void +} + +export const Route = ({ onLaunch, onModify, route }: RouteProps) => { + const { connected, connect, chainId } = useZodiacRoute(route.id) + const { route: currentlySelectedRoute } = useZodiacRoute() + + const [confirmClearTransactions, ConfirmationModal] = + useConfirmClearTransactions() + + const handleLaunch = async () => { + // we continue working with the same avatar, so don't have to clear the recorded transaction + const keepTransactionBundle = + currentlySelectedRoute && currentlySelectedRoute.avatar === route.avatar + + const confirmed = + keepTransactionBundle || (await confirmClearTransactions()) + + if (!confirmed) { + return + } + + if (connected) { + onLaunch(route.id) + return + } + + if (!connected && connect) { + const success = await connect() + if (success) { + onLaunch(route.id) + return + } + } + + onModify(route.id) + } + + return ( + <> +
+ + +
+ + + {connected ? ( + Pilot wallet is connected + ) : connect ? ( + + Pilot wallet is connected to a different chain + + ) : ( + + Pilot wallet is not connected + + )} + +

+ {route.label || Unnamed route} + +
+
+ Last Used +
+
+ {route.lastUsed ? ( + `${formatDistanceToNow(route.lastUsed)} ago` + ) : ( + <>N/A + )} +
+
+

+
+ + onModify(route.id)} + className="bg-none px-4 py-1 before:content-none" + > + Modify + +
+ +
+ +
+
+
+
+ + + ) +} diff --git a/extension/src/panel/app-routes/list-routes/style.module.css b/extension/src/panel/app-routes/list-routes/style.module.css index 24f40e7c..9da29a54 100644 --- a/extension/src/panel/app-routes/list-routes/style.module.css +++ b/extension/src/panel/app-routes/list-routes/style.module.css @@ -1,33 +1,8 @@ -h2 { - font-size: 1.5em; - margin: 0em; -} - -button.removeButton { - height: auto; - width: auto; - aspect-ratio: 1/1; - padding: 0 8px; - border: 1px solid crimson; -} - -button.removeButton:hover { - background-color: rgba(220, 20, 60, 0.2); -} - -.addConnection { - width: auto; - padding: 4px 24px; -} - .connection { position: relative; } .connectionItemContainer { - width: 100%; - background-color: rgba(39, 38, 30, 0.7); - border-color: rgba(255, 255, 255, 0.3); box-shadow: 0px 2px 10px 0px rgba(0, 0, 0, 0.1); padding: var(--spacing-4); } @@ -46,69 +21,10 @@ button.removeButton:hover { padding-top: 34px; } -.connectionStack { - min-width: 505px; -} - -.modifyButton { - position: absolute !important; - top: var(--spacing-4); - right: var(--spacing-4); - background: none; - padding: var(--spacing-1) var(--spacing-3); -} - -.modifyButton:before { - content: none; -} - -.connectionIcon { - position: relative; - border: 1px solid; - padding: 0; - border-color: var(--border-color); - border-radius: 50px; - background: rgb(0 0 0 / 0.3); - display: flex; - align-items: center; - justify-content: center; - margin: 4px; -} -.connectionIcon:before { - content: ' '; - position: absolute; - z-index: 1; - top: -4px; - left: -4px; - right: -4px; - bottom: -4px; - border: 1px solid var(--border-color); - pointer-events: none; - border-radius: 50px; -} - -.connectionIcon svg { - border-radius: 50px; -} - .infoContainer { padding: 0 0 0 45px; } -.labelContainer { - overflow: hidden; -} - -.labelContainer h2 { - font-size: 1.4em; - margin-bottom: 0; - line-height: 1.2; - text-align: left; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - .infoDatum { font-family: 'Roboto Mono', monospace; } diff --git a/extension/src/panel/app-routes/transactions/RolePermissionCheck.tsx b/extension/src/panel/app-routes/transactions/RolePermissionCheck.tsx index c26a0c6a..98b8de4b 100644 --- a/extension/src/panel/app-routes/transactions/RolePermissionCheck.tsx +++ b/extension/src/panel/app-routes/transactions/RolePermissionCheck.tsx @@ -1,8 +1,8 @@ import { Flex, Tag } from '@/components' import { useProvider } from '@/providers' import { TransactionState } from '@/state' -import { Eip1193Provider, JsonRpcError, Route } from '@/types' -import { useRoute } from '@/zodiac-routes' +import { Eip1193Provider, JsonRpcError, ZodiacRoute } from '@/types' +import { useZodiacRoute } from '@/zodiac-routes' import { MetaTransactionData } from '@safe-global/safe-core-sdk-types' import { toQuantity, ZeroAddress } from 'ethers' import React, { useEffect, useState } from 'react' @@ -22,7 +22,7 @@ import classes from './style.module.css' const simulateRolesTransaction = async ( encodedTransaction: MetaTransactionData, - route: Route, + route: ZodiacRoute, provider: Eip1193Provider ) => { const routeWithInitiator = ( @@ -82,7 +82,7 @@ const RolePermissionCheck: React.FC<{ mini?: boolean }> = ({ transactionState, index, mini = false }) => { const [error, setError] = useState(undefined) - const { route } = useRoute() + const { route } = useZodiacRoute() const provider = useProvider() const translationAvailable = !!useApplicableTranslation( diff --git a/extension/src/panel/app-routes/transactions/RouteBubble/index.tsx b/extension/src/panel/app-routes/transactions/RouteBubble/index.tsx index 8d81f2d3..44b234f7 100644 --- a/extension/src/panel/app-routes/transactions/RouteBubble/index.tsx +++ b/extension/src/panel/app-routes/transactions/RouteBubble/index.tsx @@ -1,5 +1,5 @@ import { Blockie, Box, ConnectionStack } from '@/components' -import { useRoute } from '@/zodiac-routes' +import { useZodiacRoute } from '@/zodiac-routes' import React from 'react' import { Link } from 'react-router-dom' import { asLegacyConnection } from '../../legacyConnectionMigrations' @@ -7,7 +7,7 @@ import ConnectionsIcon from './ConnectionsIcon' import classes from './style.module.css' export const RouteBubble: React.FC = () => { - const { route, chainId } = useRoute() + const { route, chainId } = useZodiacRoute() const connection = asLegacyConnection(route) return ( diff --git a/extension/src/panel/app-routes/transactions/Submit.tsx b/extension/src/panel/app-routes/transactions/Submit.tsx index 4be8bfa4..ebc0d671 100644 --- a/extension/src/panel/app-routes/transactions/Submit.tsx +++ b/extension/src/panel/app-routes/transactions/Submit.tsx @@ -3,7 +3,7 @@ import { Button, IconButton, RawAddress, toastClasses } from '@/components' import { getReadOnlyProvider, useSubmitTransactions } from '@/providers' import { useTransactions } from '@/state' import { JsonRpcError, ProviderType } from '@/types' -import { useRoute } from '@/zodiac-routes' +import { useZodiacRoute } from '@/zodiac-routes' import React, { useState } from 'react' import { RiCloseLine, RiExternalLinkLine } from 'react-icons/ri' import Modal, { Styles } from 'react-modal' @@ -19,7 +19,7 @@ import { import classes from './style.module.css' const Submit: React.FC = () => { - const { route, chainId, connect, connected } = useRoute() + const { route, chainId, connect, connected } = useZodiacRoute() const { initiator, providerType, avatar } = route const navigate = useNavigate() @@ -141,6 +141,7 @@ const Submit: React.FC = () => { <> {(connected || !!connect) && (