Skip to content

Commit

Permalink
chore: clear transactions on special route (#392)
Browse files Browse the repository at this point in the history
* list routes redirects to clear-transactions route instead of doing the work

* add failing test

* redirect from edit route to new clear transactions route

* implement clear transactions route
  • Loading branch information
frontendphil authored Dec 18, 2024
1 parent 500e50a commit 93bfb6f
Show file tree
Hide file tree
Showing 15 changed files with 200 additions and 31 deletions.
4 changes: 3 additions & 1 deletion extension/src/components/InlineForm.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import type { ComponentPropsWithRef } from 'react'
import { Form } from 'react-router'

export type InlineFormContext = Record<string, string | number | undefined>

type InlineFormProps = ComponentPropsWithRef<typeof Form> & {
context?: Record<string, string | number | undefined>
context?: InlineFormContext
intent?: string
}

Expand Down
1 change: 1 addition & 0 deletions extension/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { expectRouteToBe, render } from '@/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { action, ClearTransactions } from './ClearTransactions'

const { mockClearTransactions } = vi.hoisted(() => ({
mockClearTransactions: vi.fn(),
}))

vi.mock('./useClearTransactions', () => ({
useClearTransactions: () => mockClearTransactions,
}))

describe('Clear transactions', () => {
beforeEach(() => {
mockClearTransactions.mockResolvedValue(undefined)
})

it('clears all transactions', async () => {
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')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { action, ClearTransactions as Component } from './ClearTransactions'

export const ClearTransactions = {
path: 'clear-transactions/:newActiveRouteId',
element: <Component />,
action,
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -22,7 +21,5 @@ export const useClearTransactions = () => {
if (provider instanceof ForkProvider) {
await provider.deleteFork()
}
}, [provider, transactions, dispatch])

return { hasTransactions, clearTransactions }
}, [dispatch, transactions, provider])
}
4 changes: 3 additions & 1 deletion extension/src/panel/pages/$activeRouteId/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
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 = {
path: '/:activeRouteId',
element: <Component />,
loader,
children: [
{ path: '', loader: () => redirect('transactions') },
{ index: true, loader: () => redirect('transactions') },
Transactions,
ClearTransactions,
],
}
34 changes: 18 additions & 16 deletions extension/src/panel/pages/ClearTransactionsModal.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
import { GhostButton, Modal, PrimaryButton } from '@/components'
import { useClearTransactions } from './useClearTransactions'
import {
GhostButton,
InlineForm,
Modal,
PrimaryButton,
type InlineFormContext,
} from '@/components'

type ClearTransactionsModalProps = {
open: boolean
newActiveRouteId: string
additionalContext?: InlineFormContext
intent: string
onClose: () => void
onConfirm: () => void
}

export const ClearTransactionsModal = ({
open,
newActiveRouteId,
intent,
additionalContext,
onClose,
onConfirm,
}: ClearTransactionsModalProps) => {
const { clearTransactions } = useClearTransactions()

return (
<Modal
open={open}
Expand All @@ -27,16 +34,11 @@ export const ClearTransactionsModal = ({
Cancel
</GhostButton>

<PrimaryButton
style="contrast"
onClick={() => {
clearTransactions()
onClose()
onConfirm()
}}
>
Clear transactions
</PrimaryButton>
<InlineForm context={{ newActiveRouteId, ...additionalContext }}>
<PrimaryButton submit intent={intent} style="contrast">
Clear transactions
</PrimaryButton>
</InlineForm>
</Modal.Actions>
</Modal>
)
Expand Down
51 changes: 51 additions & 0 deletions extension/src/panel/pages/routes/edit.$routeId/EditRoute.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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')
})
})
})
})
29 changes: 28 additions & 1 deletion extension/src/panel/pages/routes/edit.$routeId/EditRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}`)
}
}
}

Expand Down Expand Up @@ -442,9 +467,11 @@ export const EditRoute = () => {
</Page>

<ClearTransactionsModal
newActiveRouteId={initialRouteState.id}
additionalContext={{ ...legacyConnection }}
open={confirmClearTransactions}
intent={Intent.clearTransactions}
onClose={() => setConfirmClearTransactions(false)}
onConfirm={() => submit(formRef.current, { method: 'post' })}
/>
</>
)
Expand Down
1 change: 1 addition & 0 deletions extension/src/panel/pages/routes/edit.$routeId/intents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum Intent {
saveRoute = 'saveRoute',
removeRoute = 'removeRoute',
clearTransactions = 'clearTransactions',
}
10 changes: 7 additions & 3 deletions extension/src/panel/pages/routes/list/ListRoutes.spec.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -63,6 +63,8 @@ describe('List routes', () => {
label: 'First route',
})

await saveLastUsedRouteId('firstRoute')

mockRoutes(selectedRoute, { id: 'secondRoute', label: 'Second route' })

await render(
Expand All @@ -71,7 +73,9 @@ describe('List routes', () => {
{
initialSelectedRoute: selectedRoute,
initialState: [createTransaction()],
inspectRoutes: ['/:activeRouteId'],
inspectRoutes: [
'/:activeRouteId/clear-transactions/:newActiveRouteId',
],
},
)

Expand All @@ -84,7 +88,7 @@ describe('List routes', () => {
screen.getByRole('button', { name: 'Clear transactions' }),
)

await expectRouteToBe('/secondRoute')
await expectRouteToBe('/firstRoute/clear-transactions/secondRoute')
})
})

Expand Down
12 changes: 11 additions & 1 deletion extension/src/panel/pages/routes/list/ListRoutes.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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}`,
)
}
}
}

Expand Down
5 changes: 2 additions & 3 deletions extension/src/panel/pages/routes/list/Route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 (
Expand Down Expand Up @@ -102,9 +100,10 @@ export const Route = ({ route }: RouteProps) => {
</section>

<ClearTransactionsModal
newActiveRouteId={route.id}
intent={Intent.clearTransactions}
open={confirmClearTransactions}
onClose={() => setConfirmClearTransactions(false)}
onConfirm={() => submit(formRef.current, { method: 'post' })}
/>
</>
)
Expand Down
1 change: 1 addition & 0 deletions extension/src/panel/pages/routes/list/intents.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export enum Intent {
addRoute = 'addRoute',
launchRoute = 'launchRoute',
clearTransactions = 'clearTransactions',
}

0 comments on commit 93bfb6f

Please sign in to comment.