diff --git a/e2e-tests/tests/initialLaunch.test.ts b/e2e-tests/tests/initialLaunch.test.ts index 4ab55207..9bdfbd0a 100644 --- a/e2e-tests/tests/initialLaunch.test.ts +++ b/e2e-tests/tests/initialLaunch.test.ts @@ -17,10 +17,12 @@ describe('initial launch', () => { it('should show the connections list if multiple connections are stored but the selected one is not ready', async () => { const page = await launch('gnosis') - const $doc = await getDocument(page) - await queries.findByText($doc, 'Connections') - await queries.findByText($doc, 'Gnosis DAO on GC') + await page.waitForSelector('#drawer-root') + const $drawer = await page.$('#drawer-root') + + await queries.findByText($drawer, 'Pilot Connections') + await queries.findByText($drawer, 'Gnosis DAO on GC') await page.close() await wallet.cancel() // cancel the pending connection request @@ -31,17 +33,20 @@ describe('initial launch', () => { await wallet.disconnect() const page = await launch('jan') - const $doc = await getDocument(page) + + await page.waitForSelector('#drawer-root') + const $drawer = await page.$('#drawer-root') + const $app = await page.$('#root') // approve the connection request triggered on page load and go back to Pilot page await metamask.approve() await page.bringToFront() // we will see the Connections page now - await queries.findByText($doc, 'Connections') + await queries.findByText($drawer, 'Pilot Connections') // the jan item has a status of "connected to a different chain" - const $item = await queries.findByText($doc, 'Jan Test DAO') + const $item = await queries.findByText($drawer, 'Jan Test DAO') const $status = await around($item).getByRole('status') const status = await $status.evaluate((el) => el.textContent) expect(status).toBe('Pilot wallet is connected to a different chain') @@ -59,8 +64,8 @@ describe('initial launch', () => { ) await page.bringToFront() - await queries.findByPlaceholderText($doc, 'Type a url') - await queries.findByText($doc, 'Jan Test DAO') + await queries.findByPlaceholderText($app, 'Type a url') + await queries.findByText($app, 'Jan Test DAO') await page.close() }) @@ -70,10 +75,10 @@ describe('initial launch', () => { await wallet.connectToPilot() const page = await launch('jan') + const $app = await page.$('#root') - const $doc = await getDocument(page) - await queries.findByPlaceholderText($doc, 'Type a url') - await queries.findByText($doc, 'Jan Test DAO') + await queries.findByPlaceholderText($app, 'Type a url') + await queries.findByText($app, 'Jan Test DAO') await page.close() }) }) diff --git a/extension/package.json b/extension/package.json index 1d9ed6b3..c26816c2 100644 --- a/extension/package.json +++ b/extension/package.json @@ -76,5 +76,9 @@ "typechain": "^8.0.0", "typescript": "^4.9.4", "typescript-plugin-css-modules": "^3.4.0" + }, + "dependencies": { + "moment": "^2.29.4", + "react-moment": "^1.1.3" } } diff --git a/extension/src/app.tsx b/extension/src/app.tsx index f969d214..5e42c613 100644 --- a/extension/src/app.tsx +++ b/extension/src/app.tsx @@ -7,22 +7,27 @@ import 'react-toastify/dist/ReactToastify.css' import './global.css' import Browser from './browser' -import { prependHttp } from './browser/UrlInput' +import ConnectionsDrawer from './browser/ConnectionsDrawer' +import ProvideProvider from './browser/ProvideProvider' +import { ProvideState } from './browser/state' import ZodiacToastContainer from './components/Toast' import { pushLocation } from './location' -import { ProvideMetaMask } from './providers' -import { useMatchSettingsRoute, usePushSettingsRoute } from './routing' -import Settings, { ProvideConnections, useConnection } from './settings' -import { useConnections } from './settings/connectionHooks' +import { ProvideMetaMask, ProvideTenderly } from './providers' +import { useMatchConnectionsRoute, usePushConnectionsRoute } from './routing' +import { ProvideConnections, useConnection } from './settings' +import { + useConnections, + useUpdateLastUsedConnection, +} from './settings/connectionHooks' import { validateAddress } from './utils' const Routes: React.FC = () => { - const settingsRouteMatch = useMatchSettingsRoute() - const pushSettingsRoute = usePushSettingsRoute() + const connectionsRouteMatch = useMatchConnectionsRoute() + const pushConnectionsRoute = usePushConnectionsRoute() const { connection, connected } = useConnection() - const isSettingsRoute = !!settingsRouteMatch - const settingsRequired = + const isConnectionsRoute = connectionsRouteMatch.isMatch + const connectionChangeRequired = !validateAddress(connection.avatarAddress) || !validateAddress(connection.pilotAddress) @@ -30,46 +35,49 @@ const Routes: React.FC = () => { const connectionToEdit = connections.length === 1 ? connections[0].id : undefined - const waitForWallet = !isSettingsRoute && !settingsRequired && !connected + const waitForWallet = + !isConnectionsRoute && !connectionChangeRequired && !connected - // redirect to settings page if more settings are required + useUpdateLastUsedConnection() + + // open connections drawer if a valid connection is not available useEffect(() => { - if (!isSettingsRoute && settingsRequired) { - pushSettingsRoute(connectionToEdit) + if (!isConnectionsRoute && connectionChangeRequired) { + pushConnectionsRoute(connectionToEdit) } - }, [isSettingsRoute, pushSettingsRoute, connectionToEdit, settingsRequired]) + }, [ + isConnectionsRoute, + pushConnectionsRoute, + connectionToEdit, + connectionChangeRequired, + ]) - // redirect to settings page if wallet is not connected, but only after a small delay to give the wallet time to connect when initially loading the page + // open connections drawer if wallet is not connected, but only after a small delay to give the wallet time to connect when initially loading the page useEffect(() => { let timeout: number if (waitForWallet) { timeout = window.setTimeout(() => { - pushSettingsRoute() + pushConnectionsRoute() }, 200) } return () => { window.clearTimeout(timeout) } - }, [waitForWallet, pushSettingsRoute]) + }, [waitForWallet, pushConnectionsRoute]) - if (!isSettingsRoute && settingsRequired) return null - if (!isSettingsRoute && waitForWallet) return null + if (!isConnectionsRoute && connectionChangeRequired) return null + if (!isConnectionsRoute && waitForWallet) return null - if (isSettingsRoute) { - return ( - + pushLocation(connectionsRouteMatch.url)} /> - ) - } - - return -} - -function launch(url: string) { - pushLocation(prependHttp(url)) + + + ) } const rootEl = document.getElementById('root') @@ -78,11 +86,17 @@ const root = createRoot(rootEl) root.render( - + - - + + + + + + + + - + ) diff --git a/extension/src/bridge/host.ts b/extension/src/bridge/host.ts index b1e6c2fd..c183cb14 100644 --- a/extension/src/bridge/host.ts +++ b/extension/src/bridge/host.ts @@ -1,4 +1,4 @@ -import { Eip1193Provider } from '../types' +import { Connection, Eip1193Provider } from '../types' interface Request { method: string @@ -7,16 +7,30 @@ interface Request { export default class BridgeHost { private provider: Eip1193Provider + private connection: Connection private source: WindowProxy | undefined - constructor(provider: Eip1193Provider) { + constructor(provider: Eip1193Provider, connection: Connection) { this.provider = provider + this.connection = connection } setProvider(provider: Eip1193Provider) { this.provider = provider } + setConnection(connection: Connection) { + if (connection.avatarAddress !== this.connection.avatarAddress) { + this.emitBridgeEvent('accountsChanged', [[connection.avatarAddress]]) + } + + if (connection.chainId !== this.connection.chainId) { + this.emitBridgeEvent('chainChanged', [connection.chainId]) + } + + this.connection = connection + } + initBridge(event: MessageEvent) { if (!event.source) throw new Error('Unable to get message source') if ( @@ -28,6 +42,19 @@ export default class BridgeHost { this.source = event.source } + private emitBridgeEvent(event: string, args: any[]) { + if (!this.source) throw new Error('source must be set') + + this.source.postMessage( + { + zodiacPilotBridgeEvent: true, + event, + args, + }, + '*' + ) + } + private async handleRequest(request: Request, messageId: number) { console.debug('REQ', messageId, request) if (!this.source) throw new Error('source must be set') @@ -54,7 +81,6 @@ export default class BridgeHost { handleMessage(ev: MessageEvent) { const { zodiacPilotBridgeInit, - zodiacPilotBridgeRequest, messageId, request, diff --git a/extension/src/bridge/iframe.ts b/extension/src/bridge/iframe.ts index a7832050..9fafe6c2 100644 --- a/extension/src/bridge/iframe.ts +++ b/extension/src/bridge/iframe.ts @@ -34,6 +34,16 @@ export default class BridgeIframe extends EventEmitter { chainId, }) }) + + const handleBridgeEvent = (ev: MessageEvent) => { + const { zodiacPilotBridgeEvent, event, args } = ev.data + if (!zodiacPilotBridgeEvent) { + return + } + this.emit(event, ...args) + } + + window.addEventListener('message', handleBridgeEvent) } request(request: JsonRpcRequest): Promise { diff --git a/extension/src/browser/ConnectionsDrawer/ConnectIcon.tsx b/extension/src/browser/ConnectionsDrawer/ConnectIcon.tsx new file mode 100644 index 00000000..b03d3f72 --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/ConnectIcon.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import { IconType } from 'react-icons/lib' + +const ConnectIcon: IconType = ({ size, color, title, role }) => ( + + {title && {title}} + + +) + +export default ConnectIcon diff --git a/extension/src/browser/ConnectionsDrawer/Edit/index.tsx b/extension/src/browser/ConnectionsDrawer/Edit/index.tsx new file mode 100644 index 00000000..4139711c --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/Edit/index.tsx @@ -0,0 +1,274 @@ +import { KnownContracts } from '@gnosis.pm/zodiac' +import React, { useEffect } from 'react' +import { RiDeleteBinLine } from 'react-icons/ri' + +import { Box, Button, Field, Flex, IconButton } from '../../../components' +import { useConfirmationModal } from '../../../components/ConfirmationModal' +import { useConnectionsHash, usePushConnectionsRoute } from '../../../routing' +import { useSafesWithOwner } from '../../../safe' +import { useSafeDelegates } from '../../../safe' +import AvatarInput from '../../../settings/Connection/AvatarInput' +import ConnectButton from '../../../settings/Connection/ConnectButton' +import ModSelect, { + NO_MODULE_OPTION, + Option, +} from '../../../settings/Connection/ModSelect' +import { + MODULE_NAMES, + SupportedModuleType, + useZodiacModules, +} from '../../../settings/Connection/useZodiacModules' +import { + useConnection, + useConnections, + useSelectedConnectionId, +} from '../../../settings/connectionHooks' +import useConnectionDryRun from '../../../settings/useConnectionDryRun' +import { useClearTransactions } from '../../state/transactionHooks' + +import classes from './style.module.css' + +interface Props { + connectionId: string + onLaunched: () => void +} + +type ConnectionPatch = { + label?: string + avatarAddress?: string + moduleAddress?: string + moduleType?: SupportedModuleType + roleId?: string +} + +const EditConnection: React.FC = ({ connectionId, onLaunched }) => { + const [connections, setConnections] = useConnections() + const { connection, connected, connect } = useConnection(connectionId) + const connectionsHash = useConnectionsHash() + const [, selectConnection] = useSelectedConnectionId() + const pushConnectionsRoute = usePushConnectionsRoute() + + useEffect(() => { + const exists = connections.some((c) => c.id === connectionId) + + if (!exists) { + pushConnectionsRoute() + } + }, [connectionId, connections, pushConnectionsRoute]) + + const { label, avatarAddress, pilotAddress, moduleAddress, roleId } = + connection + + const { safes } = useSafesWithOwner(pilotAddress, connectionId) + const { delegates } = useSafeDelegates(avatarAddress, connectionId) + + // TODO modules is a nested list, but we currently only render the top-level items + const { + loading: loadingMods, + isValidSafe, + modules, + } = useZodiacModules(avatarAddress, connectionId) + + const { hasTransactions, clearTransactions } = useClearTransactions() + const [getConfirmation, ConfirmationModal] = useConfirmationModal() + + const handleCanLaunch = async () => { + if (!hasTransactions) { + return true + } + + const confirmation = await getConfirmation( + 'Switching connections will empty your current transaction bundle.' + ) + + if (!confirmation) { + return false + } + + clearTransactions() + + return true + } + + const selectedModule = moduleAddress + ? modules.find((mod) => mod.moduleAddress === moduleAddress) + : undefined + + const handleUpdateConnection = (patch: ConnectionPatch) => { + setConnections( + connections.map((c) => + c.id === connection.id ? { ...connection, ...patch } : c + ) + ) + } + + const handleRemoveConnection = () => { + const newConnections = connections.filter((c) => c.id !== connection.id) + setConnections(newConnections) + pushConnectionsRoute() + } + + const handleLaunchConnection = async () => { + const canLaunch = await handleCanLaunch() + + if (!canLaunch) { + return + } + + if (connected) { + selectConnection(connection.id) + onLaunched() + return + } + + if (!connected && connect) { + const success = await connect() + if (success) { + selectConnection(connection.id) + onLaunched() + return + } + } + } + + const error = useConnectionDryRun(connection) + + const pilotIsOwner = safes.some( + (safe) => safe.toLowerCase() === avatarAddress.toLowerCase() + ) + const pilotIsDelegate = delegates.some( + (delegate) => delegate.toLowerCase() === pilotAddress.toLowerCase() + ) + const defaultModOption = + pilotIsOwner || pilotIsDelegate ? NO_MODULE_OPTION : '' + + const canLaunch = connected || !!connect + const canRemove = connections.length > 1 + + return ( + <> + + + + +

{connection.label || 'New connection'}

+ + ← All Connections + +
+ + + + + + +
+
+
+ + + {error && ( + +
+

There seems to be a problem with this connection:

+ + {error} + +
+
+ )} + + { + handleUpdateConnection({ + label: ev.target.value, + }) + }} + /> + + + + + + + handleUpdateConnection({ + avatarAddress: address, + moduleAddress: '', + moduleType: undefined, + }) + } + /> + + + ({ + value: mod.moduleAddress, + label: `${MODULE_NAMES[mod.type]} Mod`, + })), + ]} + onChange={(selected) => { + const mod = modules.find( + (mod) => mod.moduleAddress === (selected as Option).value + ) + handleUpdateConnection({ + moduleAddress: mod?.moduleAddress, + moduleType: mod?.type, + }) + }} + value={ + selectedModule + ? { + value: selectedModule.moduleAddress, + label: MODULE_NAMES[selectedModule.type], + } + : defaultModOption + } + isDisabled={loadingMods || !isValidSafe} + placeholder={ + loadingMods || !isValidSafe ? '' : 'Select a module' + } + avatarAddress={avatarAddress} + /> + + {selectedModule?.type === KnownContracts.ROLES && ( + + { + handleUpdateConnection({ roleId: ev.target.value }) + }} + placeholder="0" + /> + + )} +
+
+
+ + + ) +} + +export default EditConnection diff --git a/extension/src/browser/ConnectionsDrawer/Edit/style.module.css b/extension/src/browser/ConnectionsDrawer/Edit/style.module.css new file mode 100644 index 00000000..9d1caadc --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/Edit/style.module.css @@ -0,0 +1,144 @@ +.editContainer { + position: relative; +} +h2 { + font-size: 1.5em; + margin: 0em; +} + +hr { + border: none; + height: 1px; + background-color: rgba(217, 212, 173, 0.4); + margin: 0.5em 0 0 0; +} + +.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; +} +.status { + position: relative; + border: 1px solid; + 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; +} +.status: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; +} + +.status svg { + border-radius: 50px; +} + +.labelContainer { + overflow: hidden; +} + +.labelContainer h3 { + font-size: 1.4em; + margin-bottom: 0; + line-height: 1.2; + text-align: left; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; +} + +.infoContainer { + padding: 0 0 0 45px; +} + +.connectionItem { + position: relative; +} +button.connectionButton { + width: 100%; + padding: calc(var(--spacing-4)); + background-color: rgba(39, 38, 30, 0.7); +} +button.connectionEdit { + position: relative; + z-index: 10; + width: auto; + background: none; + border-style: solid; + border-width: 1px; +} +.connectionButton:hover .addressHelper { + opacity: 1; +} + +.connectionButton:hover .addressBox, +.connectionButton:hover .addressBox:before { + border-color: rgba(217, 212, 173, 0.5); +} + +.errorIcon { + color: crimson; + font-size: 1.7em; +} + +.errorInfo { + flex-grow: 1; +} + +.error { + margin-top: 12px; + color: crimson; + background: #f4433640; + border: none; + font-family: 'Roboto Mono', monospace; + font-size: 14px; +} + +.infoDatum { + font-family: 'Roboto Mono', monospace; +} + +.infoLabel { + color: #d9d4ad; +} + +.info { + font-size: 14px; + gap: 20px; +} diff --git a/extension/src/browser/ConnectionsDrawer/List/index.tsx b/extension/src/browser/ConnectionsDrawer/List/index.tsx new file mode 100644 index 00000000..3f05a39d --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/List/index.tsx @@ -0,0 +1,228 @@ +import { nanoid } from 'nanoid' +import React from 'react' +import Moment from 'react-moment' + +import { + Box, + BoxButton, + Button, + ConnectionStack, + Flex, +} from '../../../components' +import { useConfirmationModal } from '../../../components/ConfirmationModal' +import { usePushConnectionsRoute } from '../../../routing' +import { + ConnectedIcon, + DisconnectedIcon, +} from '../../../settings/Connection/ConnectIcon' +import { + useConnection, + useConnections, + useSelectedConnectionId, +} from '../../../settings/connectionHooks' +import { Connection, ProviderType } from '../../../types' +import { useClearTransactions } from '../../state/transactionHooks' + +import classes from './style.module.css' + +interface ConnectionsListProps { + onLaunched: () => void +} + +interface ConnectionItemProps { + connection: Connection + onLaunch: (connectionId: string) => void + onModify: (connectionId: string) => void +} + +const ConnectionItem: React.FC = ({ + onLaunch, + onModify, + connection, +}) => { + const { connected, connect } = useConnection(connection.id) + const [getConfirmation, ConfirmationModal] = useConfirmationModal() + const { hasTransactions, clearTransactions } = useClearTransactions() + + const handleCanLaunch = async () => { + if (!hasTransactions) { + return true + } + + const confirmation = await getConfirmation( + 'Switching connections will empty your current transaction bundle.' + ) + + if (!confirmation) { + return false + } + + clearTransactions() + + return true + } + + const handleModify = () => onModify(connection.id) + const handleLaunch = async () => { + const canLaunch = await handleCanLaunch() + + if (!canLaunch) { + return + } + + if (connected) { + onLaunch(connection.id) + return + } + + if (!connected && connect) { + const success = await connect() + if (success) { + onLaunch(connection.id) + return + } + } + + handleModify() + } + + return ( + <> +
+ + + + + + {connected && ( + + )} + {!connected && !connect && ( + + )} + {!connected && connect && ( + + )} + +

{connection.label}

+
+
+ + + +
+ {connection.lastUsed ? ( + + {connection.lastUsed} + + ) : ( + <>N/A + )} +
+
Last Used
+
+
+
+
+ + Modify + +
+ + + ) +} + +const ConnectionsList: React.FC = ({ onLaunched }) => { + const [, selectConnection] = useSelectedConnectionId() + const [connections, setConnections] = useConnections() + const pushConnectionsRoute = usePushConnectionsRoute() + + const handleLaunch = (connectionId: string) => { + selectConnection(connectionId) + onLaunched() + } + const handleModify = (connectionId: string) => { + pushConnectionsRoute(connectionId) + } + + const handleCreate = () => { + const id = nanoid() + setConnections([ + ...connections, + { + id, + label: '', + chainId: 1, + moduleAddress: '', + avatarAddress: '', + pilotAddress: '', + providerType: ProviderType.WalletConnect, + moduleType: undefined, + roleId: '', + }, + ]) + pushConnectionsRoute(id) + } + + return ( + + + +

Pilot Connections

+ +
+
+
+ {connections.map((connection) => ( + + ))} +
+ ) +} + +export default ConnectionsList diff --git a/extension/src/browser/ConnectionsDrawer/List/style.module.css b/extension/src/browser/ConnectionsDrawer/List/style.module.css new file mode 100644 index 00000000..905bdc70 --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/List/style.module.css @@ -0,0 +1,130 @@ +hr { + border: none; + height: 1px; + background-color: rgba(217, 212, 173, 0.4); + margin: 0.5em 0 0 0; +} + +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); +} + +.connectionItemContainer:hover, +.connectionItemContainer:hover:before, +.connectionItemContainer:hover .addressBox { + border-color: rgba(217, 212, 173, 0.5); +} + +.connectionItemContainer:hover { + box-shadow: 0px 2px 10px 0px rgba(217, 212, 173, 0.2); +} + +.connectionInfoContainer { + padding-top: 34px; +} + +.connectionStack { + min-width: 505px; +} + +.modifyButton { + position: absolute; + 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; +} + +.infoLabel { + color: #d9d4ad; +} + +.info { + font-size: 14px; + gap: 20px; +} diff --git a/extension/src/browser/ConnectionsDrawer/index.tsx b/extension/src/browser/ConnectionsDrawer/index.tsx new file mode 100644 index 00000000..e34848e2 --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/index.tsx @@ -0,0 +1,62 @@ +import React from 'react' + +import { Box, IconButton } from '../../components' +import ConnectionsIcon from '../../components/ConnectionBubble/ConnectionsIcon' +import OverlayDrawer from '../../components/OverlayDrawer' + +import EditConnection from './Edit' +import ConnectionsList from './List' +import classes from './style.module.css' + +interface ConnectionsDrawerProps { + isOpen: boolean + onClose: () => void + editConnectionId?: string +} + +interface CloseDrawerButtonProps { + onClick: () => void +} + +const CloseDrawerButton: React.FC = ({ onClick }) => ( +
+ +
+ + + + + +
+
+
+) + +const ConnectionsDrawer: React.FC = ({ + editConnectionId, + isOpen, + onClose, +}) => { + return ( + + +
+ {editConnectionId ? ( + + ) : ( + + )} +
+
+ ) +} + +export default ConnectionsDrawer diff --git a/extension/src/browser/ConnectionsDrawer/style.module.css b/extension/src/browser/ConnectionsDrawer/style.module.css new file mode 100644 index 00000000..2cfcb544 --- /dev/null +++ b/extension/src/browser/ConnectionsDrawer/style.module.css @@ -0,0 +1,43 @@ +.drawer { + min-width: 900px; + width: 50%; + max-width: 1200px; + box-sizing: border-box; + border-left: 1px solid rgba(217, 212, 173, 0.8); +} + +.toggleContainer { + position: absolute; + top: 39px; + left: 0; + transform: translate(-50%); + z-index: 9000; +} + +.toggleBackground { + background-color: rgb(30 30 18); +} + +.toggle { + width: 60px; + height: 42px; + overflow: hidden; + padding: 0; + background-color: rgba(211, 224, 173, 0.3); +} + +.toggle:hover { + border-color: rgba(217, 212, 173, 0.8); +} + +.drawerContent { + padding: 40px 40px 40px 50px; + overflow: auto; + box-sizing: border-box; + height: 100%; + scrollbar-width: none; +} + +.drawerContent::-webkit-scrollbar { + display: none; +} diff --git a/extension/src/browser/Drawer/SimulatedExecutionCheck.tsx b/extension/src/browser/Drawer/SimulatedExecutionCheck.tsx index 2c0c93ee..ea2df7d3 100644 --- a/extension/src/browser/Drawer/SimulatedExecutionCheck.tsx +++ b/extension/src/browser/Drawer/SimulatedExecutionCheck.tsx @@ -17,6 +17,7 @@ const SimulatedExecutionCheck: React.FC<{ useState(null) useEffect(() => { + if (!tenderlyProvider) return if (!transactionHash) return let canceled = false diff --git a/extension/src/browser/Frame.tsx b/extension/src/browser/Frame.tsx index bae8d9f6..7d16f53e 100644 --- a/extension/src/browser/Frame.tsx +++ b/extension/src/browser/Frame.tsx @@ -1,6 +1,7 @@ import React, { useEffect, useRef } from 'react' import BridgeHost from '../bridge/host' +import { useConnection } from '../settings' import { useProvider } from './ProvideProvider' @@ -10,15 +11,17 @@ type Props = { const BrowserFrame: React.FC = ({ src }) => { const provider = useProvider() + const { connection } = useConnection() const bridgeHostRef = useRef(null) useEffect(() => { if (!provider) return if (!bridgeHostRef.current) { - bridgeHostRef.current = new BridgeHost(provider) + bridgeHostRef.current = new BridgeHost(provider, connection) } else { bridgeHostRef.current.setProvider(provider) + bridgeHostRef.current.setConnection(connection) } const handle = (ev: MessageEvent) => { @@ -29,7 +32,7 @@ const BrowserFrame: React.FC = ({ src }) => { return () => { window.removeEventListener('message', handle) } - }, [provider]) + }, [provider, connection]) return (