From d7d94aea11f57b80b21ff6e89e2663b27b8e14db Mon Sep 17 00:00:00 2001 From: Philipp Giese Date: Wed, 18 Dec 2024 13:08:33 +0100 Subject: [PATCH 1/4] list routes redirects to clear-transactions route instead of doing the work --- .../panel/pages/ClearTransactionsModal.tsx | 26 +++++++------------ .../pages/routes/list/ListRoutes.spec.ts | 10 ++++--- .../panel/pages/routes/list/ListRoutes.tsx | 12 ++++++++- .../src/panel/pages/routes/list/Route.tsx | 5 ++-- .../src/panel/pages/routes/list/intents.ts | 1 + 5 files changed, 31 insertions(+), 23 deletions(-) diff --git a/extension/src/panel/pages/ClearTransactionsModal.tsx b/extension/src/panel/pages/ClearTransactionsModal.tsx index 83f7d1328..46fcad3f2 100644 --- a/extension/src/panel/pages/ClearTransactionsModal.tsx +++ b/extension/src/panel/pages/ClearTransactionsModal.tsx @@ -1,19 +1,18 @@ -import { GhostButton, Modal, PrimaryButton } from '@/components' -import { useClearTransactions } from './useClearTransactions' +import { GhostButton, InlineForm, Modal, PrimaryButton } from '@/components' type ClearTransactionsModalProps = { open: boolean + newActiveRouteId: string + intent: string onClose: () => void - onConfirm: () => void } export const ClearTransactionsModal = ({ open, + newActiveRouteId, + intent, onClose, - onConfirm, }: ClearTransactionsModalProps) => { - const { clearTransactions } = useClearTransactions() - return ( - { - clearTransactions() - onClose() - onConfirm() - }} - > - Clear transactions - + + + Clear transactions + + ) diff --git a/extension/src/panel/pages/routes/list/ListRoutes.spec.ts b/extension/src/panel/pages/routes/list/ListRoutes.spec.ts index 0420fba7c..e640a6ea1 100644 --- a/extension/src/panel/pages/routes/list/ListRoutes.spec.ts +++ b/extension/src/panel/pages/routes/list/ListRoutes.spec.ts @@ -1,5 +1,5 @@ import { ETH_ZERO_ADDRESS, ZERO_ADDRESS } from '@/chains' -import { getRoutes } from '@/execution-routes' +import { getRoutes, saveLastUsedRouteId } from '@/execution-routes' import { connectMockWallet, createMockRoute, @@ -63,6 +63,8 @@ describe('List routes', () => { label: 'First route', }) + await saveLastUsedRouteId('firstRoute') + mockRoutes(selectedRoute, { id: 'secondRoute', label: 'Second route' }) await render( @@ -71,7 +73,9 @@ describe('List routes', () => { { initialSelectedRoute: selectedRoute, initialState: [createTransaction()], - inspectRoutes: ['/:activeRouteId'], + inspectRoutes: [ + '/:activeRouteId/clear-transactions/:newActiveRouteId', + ], }, ) @@ -84,7 +88,7 @@ describe('List routes', () => { screen.getByRole('button', { name: 'Clear transactions' }), ) - await expectRouteToBe('/secondRoute') + await expectRouteToBe('/firstRoute/clear-transactions/secondRoute') }) }) diff --git a/extension/src/panel/pages/routes/list/ListRoutes.tsx b/extension/src/panel/pages/routes/list/ListRoutes.tsx index e866e83e2..40a73814b 100644 --- a/extension/src/panel/pages/routes/list/ListRoutes.tsx +++ b/extension/src/panel/pages/routes/list/ListRoutes.tsx @@ -1,5 +1,5 @@ import { Breadcrumbs, InlineForm, Page, PrimaryButton } from '@/components' -import { createRoute, getRoutes } from '@/execution-routes' +import { createRoute, getLastUsedRouteId, getRoutes } from '@/execution-routes' import { getString } from '@/utils' import { Plus } from 'lucide-react' import { redirect, useLoaderData, type ActionFunctionArgs } from 'react-router' @@ -27,6 +27,16 @@ export const action = async ({ request }: ActionFunctionArgs) => { return redirect(`/${routeId}`) } + + case Intent.clearTransactions: { + const currentlyActiveRouteId = await getLastUsedRouteId() + + const newActiveRouteId = getString(data, 'newActiveRouteId') + + return redirect( + `/${currentlyActiveRouteId}/clear-transactions/${newActiveRouteId}`, + ) + } } } diff --git a/extension/src/panel/pages/routes/list/Route.tsx b/extension/src/panel/pages/routes/list/Route.tsx index b79eb8fb8..f6d744d17 100644 --- a/extension/src/panel/pages/routes/list/Route.tsx +++ b/extension/src/panel/pages/routes/list/Route.tsx @@ -10,7 +10,6 @@ import type { ExecutionRoute } from '@/types' import { formatDistanceToNow } from 'date-fns' import { Cable, PlugZap, Unplug } from 'lucide-react' import { useRef, useState } from 'react' -import { useSubmit } from 'react-router' import { ClearTransactionsModal } from '../../ClearTransactionsModal' import { ConnectionStack } from '../../ConnectionStack' import { asLegacyConnection } from '../../legacyConnectionMigrations' @@ -26,7 +25,6 @@ export const Route = ({ route }: RouteProps) => { const [confirmClearTransactions, setConfirmClearTransactions] = useState(false) const transactions = useTransactions() - const submit = useSubmit() const formRef = useRef(null) return ( @@ -102,9 +100,10 @@ export const Route = ({ route }: RouteProps) => { setConfirmClearTransactions(false)} - onConfirm={() => submit(formRef.current, { method: 'post' })} /> ) diff --git a/extension/src/panel/pages/routes/list/intents.ts b/extension/src/panel/pages/routes/list/intents.ts index a6d46f1b7..3f9fd8702 100644 --- a/extension/src/panel/pages/routes/list/intents.ts +++ b/extension/src/panel/pages/routes/list/intents.ts @@ -1,4 +1,5 @@ export enum Intent { addRoute = 'addRoute', launchRoute = 'launchRoute', + clearTransactions = 'clearTransactions', } From b5fcf7aa8ed30eb1fd214e230410e21b645ebdf3 Mon Sep 17 00:00:00 2001 From: Philipp Giese Date: Wed, 18 Dec 2024 13:40:43 +0100 Subject: [PATCH 2/4] add failing test --- .../ClearTransactions.spec.ts | 9 +++++++++ .../ClearTransactions.tsx | 1 + .../clear-transactions.$newActiveRouteId/index.tsx | 6 ++++++ extension/src/panel/pages/$activeRouteId/index.tsx | 2 ++ 4 files changed, 18 insertions(+) create mode 100644 extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts create mode 100644 extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx create mode 100644 extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts new file mode 100644 index 000000000..ff07d8645 --- /dev/null +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts @@ -0,0 +1,9 @@ +import { describe, it, vi } from 'vitest' + +vi.mock('') + +describe('Clear transactions', () => { + it('clears all transactions', async () => { + throw new Error('Fail') + }) +}) diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx new file mode 100644 index 000000000..4429198a2 --- /dev/null +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx @@ -0,0 +1 @@ +export const ClearTransactions = () => null diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx new file mode 100644 index 000000000..c30060118 --- /dev/null +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx @@ -0,0 +1,6 @@ +import { ClearTransactions as Component } from './ClearTransactions' + +export const ClearTransactions = { + path: 'clear-transactions/:newActiveRouteId', + element: , +} diff --git a/extension/src/panel/pages/$activeRouteId/index.tsx b/extension/src/panel/pages/$activeRouteId/index.tsx index 8e5f591d4..22cf9c6fa 100644 --- a/extension/src/panel/pages/$activeRouteId/index.tsx +++ b/extension/src/panel/pages/$activeRouteId/index.tsx @@ -1,5 +1,6 @@ import { redirect, type RouteObject } from 'react-router' import { ActiveRoute as Component, loader } from './ActiveRoute' +import { ClearTransactions } from './clear-transactions.$newActiveRouteId' import { Transactions } from './transactions' export const ActiveRoute: RouteObject = { @@ -9,5 +10,6 @@ export const ActiveRoute: RouteObject = { children: [ { path: '', loader: () => redirect('transactions') }, Transactions, + ClearTransactions, ], } From 29dba9bad0e6535436ca4e3da3bcf25e9d9e9fb6 Mon Sep 17 00:00:00 2001 From: Philipp Giese Date: Wed, 18 Dec 2024 14:46:14 +0100 Subject: [PATCH 3/4] redirect from edit route to new clear transactions route --- extension/src/components/InlineForm.tsx | 4 +- extension/src/components/index.ts | 1 + .../panel/pages/ClearTransactionsModal.tsx | 12 ++++- .../routes/edit.$routeId/EditRoute.spec.tsx | 51 +++++++++++++++++++ .../pages/routes/edit.$routeId/EditRoute.tsx | 29 ++++++++++- .../pages/routes/edit.$routeId/intents.ts | 1 + 6 files changed, 94 insertions(+), 4 deletions(-) diff --git a/extension/src/components/InlineForm.tsx b/extension/src/components/InlineForm.tsx index 56303b29f..91dafcc87 100644 --- a/extension/src/components/InlineForm.tsx +++ b/extension/src/components/InlineForm.tsx @@ -1,8 +1,10 @@ import type { ComponentPropsWithRef } from 'react' import { Form } from 'react-router' +export type InlineFormContext = Record + type InlineFormProps = ComponentPropsWithRef & { - context?: Record + context?: InlineFormContext intent?: string } diff --git a/extension/src/components/index.ts b/extension/src/components/index.ts index 7547015fe..26f4a8f6a 100644 --- a/extension/src/components/index.ts +++ b/extension/src/components/index.ts @@ -7,6 +7,7 @@ export { ConfirmationModal, useConfirmationModal } from './ConfirmationModal' export { CopyToClipboard } from './CopyToClipboard' export { Divider } from './Divider' export { InlineForm } from './InlineForm' +export type { InlineFormContext } from './InlineForm' export * from './inputs' export * from './logos' export { Modal } from './Modal' diff --git a/extension/src/panel/pages/ClearTransactionsModal.tsx b/extension/src/panel/pages/ClearTransactionsModal.tsx index 46fcad3f2..56d79ffd5 100644 --- a/extension/src/panel/pages/ClearTransactionsModal.tsx +++ b/extension/src/panel/pages/ClearTransactionsModal.tsx @@ -1,8 +1,15 @@ -import { GhostButton, InlineForm, Modal, PrimaryButton } from '@/components' +import { + GhostButton, + InlineForm, + Modal, + PrimaryButton, + type InlineFormContext, +} from '@/components' type ClearTransactionsModalProps = { open: boolean newActiveRouteId: string + additionalContext?: InlineFormContext intent: string onClose: () => void } @@ -11,6 +18,7 @@ export const ClearTransactionsModal = ({ open, newActiveRouteId, intent, + additionalContext, onClose, }: ClearTransactionsModalProps) => { return ( @@ -26,7 +34,7 @@ export const ClearTransactionsModal = ({ Cancel - + Clear transactions diff --git a/extension/src/panel/pages/routes/edit.$routeId/EditRoute.spec.tsx b/extension/src/panel/pages/routes/edit.$routeId/EditRoute.spec.tsx index 16613663e..51c6e47fc 100644 --- a/extension/src/panel/pages/routes/edit.$routeId/EditRoute.spec.tsx +++ b/extension/src/panel/pages/routes/edit.$routeId/EditRoute.spec.tsx @@ -598,6 +598,57 @@ describe('Edit Zodiac route', () => { screen.queryByRole('dialog', { name: 'Clear transactions' }), ).not.toBeInTheDocument() }) + + it('is possible to launch a new route and clear transactions', async () => { + const selectedRoute = createMockRoute({ + id: 'firstRoute', + label: 'First route', + avatar: randomPrefixedAddress(), + }) + + await mockRoutes(selectedRoute, { + id: 'another-route', + avatar: randomPrefixedAddress(), + }) + await saveLastUsedRouteId('another-route') + + await render( + '/routes/edit/firstRoute', + [ + { + path: '/routes/edit/:routeId', + Component: EditRoute, + loader, + action, + }, + ], + { + initialState: [createTransaction()], + inspectRoutes: [ + '/:activeRouteId/clear-transactions/:newActiveRouteId', + ], + }, + ) + + await userEvent.click( + screen.getByRole('button', { name: 'Clear piloted Safe' }), + ) + + await userEvent.type( + screen.getByRole('textbox', { name: 'Piloted Safe' }), + randomAddress(), + ) + + await userEvent.click( + screen.getByRole('button', { name: 'Save & Launch' }), + ) + + await userEvent.click( + screen.getByRole('button', { name: 'Clear transactions' }), + ) + + await expectRouteToBe('/another-route/clear-transactions/firstRoute') + }) }) }) }) diff --git a/extension/src/panel/pages/routes/edit.$routeId/EditRoute.tsx b/extension/src/panel/pages/routes/edit.$routeId/EditRoute.tsx index 363b17271..3144b3c44 100644 --- a/extension/src/panel/pages/routes/edit.$routeId/EditRoute.tsx +++ b/extension/src/panel/pages/routes/edit.$routeId/EditRoute.tsx @@ -167,6 +167,31 @@ export const action = async ({ params, request }: ActionFunctionArgs) => { return redirect(`/${routeId}`) } + + case Intent.clearTransactions: { + const lastUsedRouteId = await getLastUsedRouteId() + + await saveRoute( + fromLegacyConnection({ + id: routeId, + label: getString(data, 'label'), + chainId: getInt(data, 'chainId') as ChainId, + avatarAddress: getString(data, 'avatarAddress'), + moduleAddress: getString(data, 'moduleAddress'), + pilotAddress: getString(data, 'pilotAddress'), + providerType: getInt(data, 'providerType'), + moduleType: getOptionalString( + data, + 'moduleType', + ) as SupportedModuleType, + multisend: getOptionalString(data, 'multisend'), + multisendCallOnly: getOptionalString(data, 'multisendCallOnly'), + roleId: getOptionalString(data, 'roleId'), + }), + ) + + return redirect(`/${lastUsedRouteId}/clear-transactions/${routeId}`) + } } } @@ -442,9 +467,11 @@ export const EditRoute = () => { setConfirmClearTransactions(false)} - onConfirm={() => submit(formRef.current, { method: 'post' })} /> ) diff --git a/extension/src/panel/pages/routes/edit.$routeId/intents.ts b/extension/src/panel/pages/routes/edit.$routeId/intents.ts index 55dcbe788..827a4cd37 100644 --- a/extension/src/panel/pages/routes/edit.$routeId/intents.ts +++ b/extension/src/panel/pages/routes/edit.$routeId/intents.ts @@ -1,4 +1,5 @@ export enum Intent { saveRoute = 'saveRoute', removeRoute = 'removeRoute', + clearTransactions = 'clearTransactions', } From 49fcfb7969b63465e7727b0bd241057fcb3af5bf Mon Sep 17 00:00:00 2001 From: Philipp Giese Date: Wed, 18 Dec 2024 15:27:24 +0100 Subject: [PATCH 4/4] implement clear transactions route --- .../ClearTransactions.spec.ts | 42 +++++++++++++++++-- .../ClearTransactions.tsx | 21 +++++++++- .../index.tsx | 3 +- .../useClearTransactions.ts | 7 +--- .../src/panel/pages/$activeRouteId/index.tsx | 2 +- 5 files changed, 64 insertions(+), 11 deletions(-) rename extension/src/panel/pages/{ => $activeRouteId/clear-transactions.$newActiveRouteId}/useClearTransactions.ts (74%) diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts index ff07d8645..4c46e7883 100644 --- a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.spec.ts @@ -1,9 +1,45 @@ -import { describe, it, vi } from 'vitest' +import { expectRouteToBe, render } from '@/test-utils' +import { beforeEach, describe, expect, it, vi } from 'vitest' +import { action, ClearTransactions } from './ClearTransactions' -vi.mock('') +const { mockClearTransactions } = vi.hoisted(() => ({ + mockClearTransactions: vi.fn(), +})) + +vi.mock('./useClearTransactions', () => ({ + useClearTransactions: () => mockClearTransactions, +})) describe('Clear transactions', () => { + beforeEach(() => { + mockClearTransactions.mockResolvedValue(undefined) + }) + it('clears all transactions', async () => { - throw new Error('Fail') + await render('/test-route/clear-transactions/new-route', [ + { + path: '/:activeRouteId/clear-transactions/:newActiveRouteId', + Component: ClearTransactions, + action, + }, + ]) + + expect(mockClearTransactions).toHaveBeenCalled() + }) + + it('redirects to the new active route', async () => { + await render( + '/test-route/clear-transactions/new-route', + [ + { + path: '/:activeRouteId/clear-transactions/:newActiveRouteId', + Component: ClearTransactions, + action, + }, + ], + { inspectRoutes: ['/:activeRouteId'] }, + ) + + await expectRouteToBe('/new-route') }) }) diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx index 4429198a2..159200a54 100644 --- a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/ClearTransactions.tsx @@ -1 +1,20 @@ -export const ClearTransactions = () => null +import { useEffect } from 'react' +import { redirect, useSubmit, type ActionFunctionArgs } from 'react-router' +import { useClearTransactions } from './useClearTransactions' + +export const action = async ({ params }: ActionFunctionArgs) => { + const { newActiveRouteId } = params + + return redirect(`/${newActiveRouteId}`) +} + +export const ClearTransactions = () => { + const clearTransactions = useClearTransactions() + const submit = useSubmit() + + useEffect(() => { + clearTransactions().then(() => submit(null, { method: 'post' })) + }, [clearTransactions, submit]) + + return null +} diff --git a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx index c30060118..d63a02767 100644 --- a/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/index.tsx @@ -1,6 +1,7 @@ -import { ClearTransactions as Component } from './ClearTransactions' +import { action, ClearTransactions as Component } from './ClearTransactions' export const ClearTransactions = { path: 'clear-transactions/:newActiveRouteId', element: , + action, } diff --git a/extension/src/panel/pages/useClearTransactions.ts b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/useClearTransactions.ts similarity index 74% rename from extension/src/panel/pages/useClearTransactions.ts rename to extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/useClearTransactions.ts index 99511471b..e1bf2e23a 100644 --- a/extension/src/panel/pages/useClearTransactions.ts +++ b/extension/src/panel/pages/$activeRouteId/clear-transactions.$newActiveRouteId/useClearTransactions.ts @@ -8,8 +8,7 @@ export const useClearTransactions = () => { const provider = useProvider() const dispatch = useDispatch() - const hasTransactions = transactions.length > 0 - const clearTransactions = useCallback(async () => { + return useCallback(async () => { if (transactions.length === 0) { return } @@ -22,7 +21,5 @@ export const useClearTransactions = () => { if (provider instanceof ForkProvider) { await provider.deleteFork() } - }, [provider, transactions, dispatch]) - - return { hasTransactions, clearTransactions } + }, [dispatch, transactions, provider]) } diff --git a/extension/src/panel/pages/$activeRouteId/index.tsx b/extension/src/panel/pages/$activeRouteId/index.tsx index 22cf9c6fa..b29babf00 100644 --- a/extension/src/panel/pages/$activeRouteId/index.tsx +++ b/extension/src/panel/pages/$activeRouteId/index.tsx @@ -8,7 +8,7 @@ export const ActiveRoute: RouteObject = { element: , loader, children: [ - { path: '', loader: () => redirect('transactions') }, + { index: true, loader: () => redirect('transactions') }, Transactions, ClearTransactions, ],