Skip to content

Commit

Permalink
Add network select to network config table (#329)
Browse files Browse the repository at this point in the history
* test for preview

* Added Activate btn network config table

* Incorporated comments from slack

* Fixed network select dropdown interaction and add custom 404 pages

* Addressed PR Comments

* Addressed PR Comments

* Reverted the setSelectedNetwork in handleNetworkChange

* Updated error page

* chore: tweaking tweaks

* fix: make the wildcard network route work again

---------

Co-authored-by: Neil Campbell <[email protected]>
  • Loading branch information
lempira and neilcampbell authored Nov 27, 2024
1 parent 854ca3f commit 306e10c
Show file tree
Hide file tree
Showing 24 changed files with 251 additions and 150 deletions.
17 changes: 9 additions & 8 deletions src/App.routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ import { AppLab, appLabPageTitle } from './features/app-lab/pages/app-lab'
import { TransactionWizardPage, transactionWizardPageTitle } from './features/transaction-wizard/transaction-wizard-page'
import { RedirectPage } from './features/common/pages/redirect-page'
import { CreateAppInterfacePage, createAppInterfacePageTitle } from './features/app-interfaces/pages/create-app-interface-page'
import { EditAppInterfacePage, editAppInterfacePageTitle } from './features/app-interfaces/pages/edit-app-interface-page'
import { EditAppInterfacePage } from './features/app-interfaces/pages/edit-app-interface-page'
import { editAppInterfacePageTitle } from './features/app-interfaces/pages/labels'

export const routes = evalTemplates([
{
Expand Down Expand Up @@ -54,7 +55,7 @@ export const routes = evalTemplates([
},
{
template: Urls.Network.Explore.Transaction.ById,
errorElement: <ErrorPage title={transactionPageTitle} />,
errorElement: <ErrorPage title={transactionPageTitle} redirectUrl={Urls.Network.Explore} />,
children: [
{
template: Urls.Network.Explore.Transaction.ById,
Expand All @@ -71,29 +72,29 @@ export const routes = evalTemplates([
children: [
{
template: Urls.Network.Explore.Block.ByRound,
errorElement: <ErrorPage title={blockPageTitle} />,
errorElement: <ErrorPage title={blockPageTitle} redirectUrl={Urls.Network.Explore} />,
element: <BlockPage />,
},
{
template: Urls.Network.Explore.Block.ByRound.Group.ById,
errorElement: <ErrorPage title={groupPageTitle} />,
errorElement: <ErrorPage title={groupPageTitle} redirectUrl={Urls.Network.Explore} />,
element: <GroupPage />,
},
],
},
{
template: Urls.Network.Explore.Account.ByAddress,
element: <AccountPage />,
errorElement: <ErrorPage title={accountPageTitle} />,
errorElement: <ErrorPage title={accountPageTitle} redirectUrl={Urls.Network.Explore} />,
},
{
template: Urls.Network.Explore.Asset.ById,
element: <AssetPage />,
errorElement: <ErrorPage title={assetPageTitle} />,
errorElement: <ErrorPage title={assetPageTitle} redirectUrl={Urls.Network.Explore} />,
},
{
template: Urls.Network.Explore.Application.ById,
errorElement: <ErrorPage title={applicationPageTitle} />,
errorElement: <ErrorPage title={applicationPageTitle} redirectUrl={Urls.Network.Explore} />,
element: <ApplicationPage />,
},
{
Expand All @@ -116,7 +117,7 @@ export const routes = evalTemplates([
},
{
template: Urls.Network.AppLab.Edit.ById,
errorElement: <ErrorPage title={editAppInterfacePageTitle} />,
errorElement: <ErrorPage title={editAppInterfacePageTitle} redirectUrl={Urls.Network.AppLab} />,
element: <EditAppInterfacePage />,
},
],
Expand Down
3 changes: 2 additions & 1 deletion src/features/accounts/pages/account-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const accountFailedToLoadMessage = 'Account failed to load'

const transformError = (e: Error) => {
if (is404(e)) {
return new Error(accountInvalidAddressMessage)
e.message = accountInvalidAddressMessage
return e
}

// eslint-disable-next-line no-console
Expand Down
10 changes: 9 additions & 1 deletion src/features/app-interfaces/components/app-interface-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { AppSpecStandard } from '../data/types'
import { Button } from '@/features/common/components/button'
import { Pencil } from 'lucide-react'
import { getLatestAppSpecVersion } from '../mappers'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/features/common/components/tooltip'

type Props = {
appInterface: AppInterfaceEntity
Expand Down Expand Up @@ -52,7 +53,14 @@ export function AppInterfaceCard({ appInterface, onEdit, onDelete }: Props) {
<div className="flex items-center gap-2">
<p>Modified: {dateFormatter.asShortDate(new Date(appInterface.lastModified))}</p>
<div className="ml-auto flex gap-2">
<Button size="sm" variant="outline" icon={<Pencil size={16} />} onClick={onEdit} />
<Tooltip>
<TooltipTrigger asChild>
<Button size="icon" variant="outline" icon={<Pencil size={18} />} onClick={onEdit} title="Edit" />
</TooltipTrigger>
<TooltipContent>
<p>Edit App Interface</p>
</TooltipContent>
</Tooltip>
<DeleteAppInterfaceButton appInterface={appInterface} onDelete={onDelete} />
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,14 @@ export function DeleteAppInterfaceButton({ appInterface, onDelete }: Props) {

return (
<ConfirmButton
icon={<TrashIcon size={16} />}
size="sm"
icon={<TrashIcon size={18} />}
size="icon"
variant="destructive"
dialogHeaderText={deleteAppInterfaceLabel}
dialogContent={<div>Are you sure you want to delete '{appInterface.name}'?</div>}
dialogContent={<p className="truncate">Are you sure you want to delete '{appInterface.name}'?</p>}
onConfirm={onConfirm}
title="Delete"
tooltipContent={<p>Delete App Interface</p>}
/>
)
}
81 changes: 39 additions & 42 deletions src/features/app-interfaces/components/edit/app-specs-table.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { toast } from 'react-toastify'
import { EditAppSpecForm } from './edit-app-spec-form'
import { asAppSpecFilename } from '../../mappers'
import { Description } from '@radix-ui/react-dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from '@/features/common/components/tooltip'

const appSpecsLabel = 'App Specs'

Expand Down Expand Up @@ -45,42 +46,28 @@ export function AppSpecsTable({ appInterface, refreshAppInterface }: Props) {
header: 'Last valid round',
accessorFn: (item) => item.roundLastValid,
},

{
id: 'download',
id: 'actions',
header: '',
meta: { className: 'w-24' },
accessorFn: (item) => item,
cell: (cell) => {
const appSpec = cell.getValue<AppSpecVersion>()
return <DownloadAppSpecButton appSpec={appSpec} />
},
},
{
id: 'edit',
header: '',
meta: { className: 'w-24' },
meta: { className: 'flex' },
accessorFn: (item) => item,
cell: (cell) => {
const appSpec = cell.getValue<AppSpecVersion>()
return (
<EditAppSpecButton
applicationId={appInterface.applicationId}
appSpecIndex={cell.row.index}
appSpec={appSpec}
onSuccess={refreshAppInterface}
/>
)
},
},
{
id: 'delete',
header: '',
meta: { className: 'w-28' },
accessorFn: (item) => item,
cell: (cell) => {
return (
<DeleteAppSpecButton applicationId={appInterface.applicationId} appSpecIndex={cell.row.index} onSuccess={refreshAppInterface} />
<div className="ml-auto flex items-center gap-2">
<DownloadAppSpecButton appSpec={appSpec} />
<EditAppSpecButton
applicationId={appInterface.applicationId}
appSpecIndex={cell.row.index}
appSpec={appSpec}
onSuccess={refreshAppInterface}
/>
<DeleteAppSpecButton
applicationId={appInterface.applicationId}
appSpecIndex={cell.row.index}
onSuccess={refreshAppInterface}
/>
</div>
)
},
},
Expand Down Expand Up @@ -113,9 +100,14 @@ function DownloadAppSpecButton({ appSpec }: DownloadAppSpecButton) {
}, [appSpec])

return (
<Button variant="outline" onClick={downloadAppSpec} icon={<Download size={16} />}>
Download
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon" onClick={downloadAppSpec} icon={<Download size={18} />} title="Download" />
</TooltipTrigger>
<TooltipContent>
<p>Download App Spec</p>
</TooltipContent>
</Tooltip>
)
}

Expand Down Expand Up @@ -179,9 +171,14 @@ function EditAppSpecButton({ applicationId, appSpecIndex, appSpec, onSuccess }:

return (
<>
<Button variant="outline" onClick={openDialog} icon={<Pencil size={16} />}>
Edit
</Button>
<Tooltip>
<TooltipTrigger asChild>
<Button variant="outline" size="icon" onClick={openDialog} icon={<Pencil size={18} />} title="Edit" />
</TooltipTrigger>
<TooltipContent>
<p>Edit App Spec</p>
</TooltipContent>
</Tooltip>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen} modal={true}>
<DialogContent className="bg-card">
<Description hidden={true}>Edit an app spec</Description>
Expand Down Expand Up @@ -219,11 +216,11 @@ function DeleteAppSpecButton({ applicationId, appSpecIndex, onSuccess }: DeleteA
variant="destructive"
onConfirm={deleteExistingAppSpec}
dialogHeaderText="Delete Network?"
dialogContent={<div>Are you sure you want to delete the app spec?</div>}
icon={<Trash size={16} />}
className="w-24"
>
Delete
</ConfirmButton>
dialogContent={<p className="truncate">Are you sure you want to delete the app spec?</p>}
icon={<Trash size={18} />}
size="icon"
title="Delete"
tooltipContent={<p>Delete App Spec</p>}
/>
)
}
3 changes: 2 additions & 1 deletion src/features/app-interfaces/data/read.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useMemo } from 'react'
import { atomWithRefresh, loadable } from 'jotai/utils'
import { atom, useAtomValue, useSetAtom } from 'jotai'
import { invariant } from '@/utils/invariant'
import { appInterfaceNotFoundMessage } from '../pages/labels'

export const getAppInterface = async (dbConnection: DbConnection, applicationId: ApplicationId) => {
return await dbConnection.get('app-interfaces', applicationId)
Expand Down Expand Up @@ -37,7 +38,7 @@ export const useAppInterface = (applicationId: ApplicationId) => {
const dbConnection = await get(dbConnectionAtom)

const entity = await getAppInterface(dbConnection, applicationId)
invariant(entity, 'App interface not found')
invariant(entity, appInterfaceNotFoundMessage)
return entity
})
}, [applicationId])
Expand Down
21 changes: 12 additions & 9 deletions src/features/app-interfaces/pages/edit-app-interface-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,31 @@ import { EditAppInterface } from '../components/edit/edit-app-interface'
import { useAppInterface } from '../data'
import { invariant } from '@/utils/invariant'
import { isInteger } from '@/utils/is-integer'
import { is404 } from '@/utils/error'
import { RenderLoadable } from '@/features/common/components/render-loadable'
import { PageLoader } from '@/features/common/components/page-loader'
import { useRequiredParam } from '@/features/common/hooks/use-required-param'
import { UrlParams } from '@/routes/urls'
import { useTitle } from '@/utils/use-title'
import {
appInterfaceFailedToLoadMessage,
appInterfaceNotFoundMessage,
applicationInvalidIdMessage,
editAppInterfacePageTitle,
} from './labels'

const transformError = (e: Error) => {
if (is404(e)) {
return new Error(appInterfaceNotFoundMessage)
const transformError = (e: Error & { status?: number }) => {
// This is needed because the App interface not found doesn't return a 404 status code
if (e.message.includes(appInterfaceNotFoundMessage)) {
e.message = appInterfaceNotFoundMessage
e.status = 404
return e
}

// eslint-disable-next-line no-console
console.error(e)
return new Error(appInterfaceFailedToLoadMessage)
}

export const editAppInterfacePageTitle = 'Edit App Interface'
export const appInterfaceNotFoundMessage = 'Application Interface not found'
export const applicationInvalidIdMessage = 'Application Id is invalid'
export const appInterfaceFailedToLoadMessage = 'Application Interface failed to load'

export function EditAppInterfacePage() {
useTitle('Edit App Interface')
const { applicationId: _applicationId } = useRequiredParam(UrlParams.ApplicationId)
Expand Down
4 changes: 4 additions & 0 deletions src/features/app-interfaces/pages/labels.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const editAppInterfacePageTitle = 'Edit App Interface'
export const appInterfaceNotFoundMessage = 'Application Interface not found'
export const applicationInvalidIdMessage = 'Application Id is invalid'
export const appInterfaceFailedToLoadMessage = 'Application Interface failed to load'
3 changes: 2 additions & 1 deletion src/features/applications/pages/application-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { useTitle } from '@/utils/use-title'

const transformError = (e: Error) => {
if (is404(e)) {
return new Error(applicationNotFoundMessage)
e.message = applicationNotFoundMessage
return e
}

// eslint-disable-next-line no-console
Expand Down
3 changes: 2 additions & 1 deletion src/features/assets/pages/asset-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ import { useTitle } from '@/utils/use-title'

const transformError = (e: Error) => {
if (is404(e)) {
return new Error(assetNotFoundMessage)
e.message = assetNotFoundMessage
return e
}

// eslint-disable-next-line no-console
Expand Down
3 changes: 2 additions & 1 deletion src/features/blocks/pages/block-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import { useTitle } from '@/utils/use-title'

const transformError = (e: Error) => {
if (is404(e)) {
return new Error(blockNotFoundMessage)
e.message = blockNotFoundMessage
return e
}

// eslint-disable-next-line no-console
Expand Down
33 changes: 25 additions & 8 deletions src/features/common/components/confirm-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,47 @@ import { Button, ButtonProps } from '@/features/common/components/button'
import { Dialog, DialogContent, DialogHeader, DialogTitle, SmallSizeDialogBody } from '@/features/common/components/dialog'
import { CancelButton } from '@/features/forms/components/cancel-button'
import { Description } from '@radix-ui/react-dialog'
import { Tooltip, TooltipContent, TooltipTrigger } from './tooltip'

interface Props extends Omit<ButtonProps, 'onClick'> {
dialogHeaderText: string
dialogContent: React.ReactNode
onConfirm: () => void
tooltipContent?: React.ReactNode
}

export function ConfirmButton({ children, dialogHeaderText, dialogContent, onConfirm: onConfirmProp, ...props }: Props) {
export function ConfirmButton({ children, dialogHeaderText, dialogContent, tooltipContent, onConfirm: onConfirmProp, ...props }: Props) {
const [dialogOpen, setDialogOpen] = useState(false)

const openDialog = useCallback(() => {
setDialogOpen(true)
}, [setDialogOpen])

const closeDialog = useCallback(() => {
setDialogOpen(false)
}, [setDialogOpen])

const onConfirm = useCallback(() => {
onConfirmProp?.()
setDialogOpen(false)
}, [onConfirmProp, setDialogOpen])
closeDialog()
}, [closeDialog, onConfirmProp])

const ActionButton = (
<Button variant="outline" onClick={openDialog} {...props}>
{children}
</Button>
)

return (
<>
<Button variant="outline" onClick={openDialog} {...props}>
{children}
</Button>
{tooltipContent ? (
<Tooltip>
<TooltipTrigger asChild>{ActionButton}</TooltipTrigger>
<TooltipContent>{tooltipContent}</TooltipContent>
</Tooltip>
) : (
ActionButton
)}
<Dialog open={dialogOpen} onOpenChange={setDialogOpen} modal={true}>
<DialogContent className="bg-card">
<Description hidden={true}>Confirm action</Description>
Expand All @@ -38,8 +55,8 @@ export function ConfirmButton({ children, dialogHeaderText, dialogContent, onCon
<SmallSizeDialogBody>
{dialogContent}
<div className="mt-4 flex justify-end gap-2">
<CancelButton onClick={() => setDialogOpen(false)} className="w-28" />
<Button variant="default" onClick={onConfirm}>
<CancelButton onClick={closeDialog} className="w-28" />
<Button variant="default" onClick={onConfirm} className="w-28">
Confirm
</Button>
</div>
Expand Down
Loading

0 comments on commit 306e10c

Please sign in to comment.