Skip to content

Commit d514621

Browse files
committed
feat: implement bookmark account list
1 parent c73a382 commit d514621

12 files changed

+204
-17
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import type { BookmarkAccount } from '@workspace/db/bookmark-account/bookmark-account'
2+
import { bookmarkAccountFindMany } from '@workspace/db/bookmark-account/bookmark-account-find-many'
3+
import type { BookmarkAccountFindManyInput } from '@workspace/db/bookmark-account/bookmark-account-find-many-input'
4+
import { db } from '@workspace/db/db'
5+
import { useLiveQuery } from 'dexie-react-hooks'
6+
7+
export function useBookmarkAccountLive(input: BookmarkAccountFindManyInput = {}) {
8+
return useLiveQuery<BookmarkAccount[], BookmarkAccount[]>(() => bookmarkAccountFindMany(db, input), [], [])
9+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { useLocation } from 'react-router'
2+
3+
export function useExplorerBackButtonTo({ basePath }: { basePath: string }) {
4+
const location = useLocation()
5+
6+
const from = location.state?.from ?? basePath
7+
return from.startsWith('/') ? from : `${basePath}/${from}`
8+
}

packages/explorer/src/explorer-feature-account.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,20 @@ import { assertIsAddress } from '@solana/kit'
22
import { solanaAddressSchema } from '@workspace/db/solana/solana-address-schema'
33
import { useNetworkActive } from '@workspace/db-react/use-network-active'
44
import { UiPage } from '@workspace/ui/components/ui-page'
5-
import { useLocation, useParams } from 'react-router'
5+
import { useParams } from 'react-router'
6+
import { useExplorerBackButtonTo } from './data-access/use-explorer-back-button-to.tsx'
67
import { ExplorerFeatureAccountOverview } from './explorer-feature-account-overview.tsx'
78
import { ExplorerFeatureAccountTransactions } from './explorer-feature-account-transactions.tsx'
89
import { ExplorerUiErrorPage } from './ui/explorer-ui-error-page.tsx'
910

1011
export function ExplorerFeatureAccount({ basePath }: { basePath: string }) {
1112
const network = useNetworkActive()
12-
const location = useLocation()
13+
const backButtonTo = useExplorerBackButtonTo({ basePath })
1314
const { address } = useParams()
1415
if (!address || !solanaAddressSchema.safeParse(address).success) {
1516
return <ExplorerUiErrorPage message="The provided address is not a valid Solana address." title="Invalid address" />
1617
}
1718
assertIsAddress(address)
18-
const from = location.state?.from ?? basePath
19-
const backButtonTo = from.startsWith('/') ? from : `${basePath}/${from}`
2019

2120
return (
2221
<UiPage>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { useBookmarkAccountDelete } from '@workspace/db-react/use-bookmark-account-delete'
2+
import { useBookmarkAccountLive } from '@workspace/db-react/use-bookmark-account-live'
3+
import { useBookmarkAccountUpdate } from '@workspace/db-react/use-bookmark-account-update'
4+
import { UiCard } from '@workspace/ui/components/ui-card'
5+
import { UiPage } from '@workspace/ui/components/ui-page'
6+
import { useExplorerBackButtonTo } from './data-access/use-explorer-back-button-to.tsx'
7+
import { ExplorerUiBookmarkAccountTable } from './ui/explorer-ui-bookmark-account-table.tsx'
8+
9+
export function ExplorerFeatureBookmarkAccountList({ basePath }: { basePath: string }) {
10+
const backButtonTo = useExplorerBackButtonTo({ basePath })
11+
const items = useBookmarkAccountLive()
12+
const mutationDelete = useBookmarkAccountDelete()
13+
const mutationUpdate = useBookmarkAccountUpdate()
14+
return (
15+
<UiPage>
16+
<UiCard backButtonTo={backButtonTo} title="Account Bookmarks">
17+
<ExplorerUiBookmarkAccountTable
18+
basePath={basePath}
19+
deleteItem={async ({ address, id }) => {
20+
await mutationDelete.mutateAsync({ address, id })
21+
}}
22+
items={items}
23+
updateItem={async (item) => {
24+
await mutationUpdate.mutateAsync({
25+
address: item.address,
26+
id: item.id,
27+
input: {
28+
label: item.label,
29+
},
30+
})
31+
}}
32+
/>
33+
</UiCard>
34+
</UiPage>
35+
)
36+
}

packages/explorer/src/explorer-feature-index.tsx

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
1+
import { useBookmarkAccountLive } from '@workspace/db-react/use-bookmark-account-live'
2+
import { Button } from '@workspace/ui/components/button'
3+
import { UiCard } from '@workspace/ui/components/ui-card'
14
import { UiEmpty } from '@workspace/ui/components/ui-empty'
25
import { UiPage } from '@workspace/ui/components/ui-page'
3-
import { useNavigate } from 'react-router'
6+
import { Link, useNavigate } from 'react-router'
7+
import { ExplorerUiBookmarkAccountList } from './ui/explorer-ui-bookmark-account-list.tsx'
48
import { ExplorerUiSearch } from './ui/explorer-ui-search.tsx'
59

610
export function ExplorerFeatureIndex({ basePath }: { basePath: string }) {
711
const navigate = useNavigate()
12+
const bookmarkAccounts = useBookmarkAccountLive()
813
return (
914
<UiPage>
1015
<UiEmpty
@@ -21,6 +26,34 @@ export function ExplorerFeatureIndex({ basePath }: { basePath: string }) {
2126
/>
2227
</div>
2328
</UiEmpty>
29+
<div className="grid grid-cols-1 gap-y-2 md:grid-cols-2 md:gap-6 md:gap-y-6">
30+
<UiCard
31+
action={
32+
<Button asChild variant="outline">
33+
<Link to={`${basePath}/bookmarks/account`}>Manage</Link>
34+
</Button>
35+
}
36+
description="List of accounts you bookmarked"
37+
title="Account bookmarks"
38+
>
39+
{bookmarkAccounts.length ? (
40+
<ExplorerUiBookmarkAccountList basePath={basePath} items={bookmarkAccounts ?? []} />
41+
) : (
42+
<div className="text-center text-muted-foreground">You have no bookmarks yet.</div>
43+
)}
44+
</UiCard>
45+
<UiCard
46+
action={
47+
<Button asChild variant="outline">
48+
<Link to={`${basePath}/bookmarks/tx`}>Manage</Link>
49+
</Button>
50+
}
51+
description="List of transactions you bookmarked"
52+
title="Transaction bookmarks"
53+
>
54+
<div className="text-center text-muted-foreground">You have no bookmarks yet.</div>
55+
</UiCard>
56+
</div>
2457
</UiPage>
2558
)
2659
}

packages/explorer/src/explorer-feature-tx.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ import { solanaSignatureSchema } from '@workspace/db/solana/solana-signature-sch
33
import { useNetworkActive } from '@workspace/db-react/use-network-active'
44
import { UiCard } from '@workspace/ui/components/ui-card'
55
import { UiPage } from '@workspace/ui/components/ui-page'
6-
import { useLocation, useParams } from 'react-router'
6+
import { useParams } from 'react-router'
7+
import { useExplorerBackButtonTo } from './data-access/use-explorer-back-button-to.tsx'
78
import { ExplorerFeatureTxDetails } from './explorer-feature-tx-details.tsx'
89
import { ExplorerUiErrorPage } from './ui/explorer-ui-error-page.tsx'
910

1011
export function ExplorerFeatureTx({ basePath }: { basePath: string }) {
1112
const network = useNetworkActive()
12-
const location = useLocation()
13+
const backButtonTo = useExplorerBackButtonTo({ basePath })
1314
const { signature } = useParams() as { signature: string }
1415
if (!signature || !solanaSignatureSchema.safeParse(signature).success) {
1516
return (
@@ -20,8 +21,6 @@ export function ExplorerFeatureTx({ basePath }: { basePath: string }) {
2021
)
2122
}
2223
assertIsSignature(signature)
23-
const from = location.state?.from ?? basePath
24-
const backButtonTo = from.startsWith('/') ? from : `${basePath}/${from}`
2524

2625
return (
2726
<UiPage>

packages/explorer/src/explorer-routes.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { UiPage } from '@workspace/ui/components/ui-page'
33
import { useRoutes } from 'react-router'
44
import { ExplorerFeatureAccount } from './explorer-feature-account.tsx'
55
import { ExplorerFeatureAccountRedirect } from './explorer-feature-account-redirect.tsx'
6+
import { ExplorerFeatureBookmarkAccountList } from './explorer-feature-bookmark-account-list.tsx'
67
import { ExplorerFeatureIndex } from './explorer-feature-index.tsx'
78
import { ExplorerFeatureTx } from './explorer-feature-tx.tsx'
89

@@ -13,6 +14,7 @@ export default function ExplorerRoutes({ basePath }: { basePath: string }) {
1314
{ element: <ExplorerFeatureTx basePath={basePath} />, path: 'tx/:signature' },
1415
// This route exists to stay compatible with other explorers in the ecosystem
1516
{ element: <ExplorerFeatureAccountRedirect basePath={basePath} />, path: 'account/:address' },
17+
{ element: <ExplorerFeatureBookmarkAccountList basePath={basePath} />, path: 'bookmarks/account' },
1618
{
1719
element: (
1820
<UiPage>
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Address } from '@solana/kit'
2+
import { ellipsify } from '@workspace/ui/lib/ellipsify'
3+
4+
export function ExplorerUiAddress({ address }: { address: Address }) {
5+
return (
6+
<>
7+
<span className="hidden lg:block">{address}</span>
8+
<span className="lg:hidden" title={address}>
9+
{ellipsify(address, 6, '...')}
10+
</span>
11+
</>
12+
)
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { BookmarkAccount } from '@workspace/db/bookmark-account/bookmark-account'
2+
import { ExplorerUiLinkAddress } from './explorer-ui-link-address.tsx'
3+
4+
export function ExplorerUiBookmarkAccountList({ basePath, items }: { basePath: string; items: BookmarkAccount[] }) {
5+
return (
6+
<div className="space-y-2">
7+
{items.map((item) => (
8+
<div className="text-sm" key={item.id}>
9+
<ExplorerUiLinkAddress address={item.address} basePath={basePath} label={item.label} />
10+
</div>
11+
))}
12+
</div>
13+
)
14+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import type { BookmarkAccount } from '@workspace/db/bookmark-account/bookmark-account'
2+
import { Button } from '@workspace/ui/components/button'
3+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@workspace/ui/components/table'
4+
import { UiConfirm } from '@workspace/ui/components/ui-confirm'
5+
import { UiIcon } from '@workspace/ui/components/ui-icon'
6+
import { ExplorerUiLinkAddress } from './explorer-ui-link-address.tsx'
7+
8+
export function ExplorerUiBookmarkAccountTable({
9+
basePath,
10+
deleteItem,
11+
updateItem,
12+
items,
13+
}: {
14+
basePath: string
15+
deleteItem: (item: BookmarkAccount) => Promise<void>
16+
items: BookmarkAccount[]
17+
updateItem: (item: BookmarkAccount) => Promise<void>
18+
}) {
19+
return (
20+
<Table>
21+
<TableHeader>
22+
<TableRow className="hover:bg-transparent">
23+
<TableHead>LABEL</TableHead>
24+
<TableHead>ADDRESS</TableHead>
25+
<TableHead className="w-[120px] text-right">ACTIONS</TableHead>
26+
</TableRow>
27+
</TableHeader>
28+
<TableBody>
29+
{items.map((item) => (
30+
<TableRow key={item.id}>
31+
<TableCell className="">
32+
{item.label ? <span>{item.label}</span> : <span className="text-muted-foreground">No label</span>}
33+
</TableCell>
34+
<TableCell className="">
35+
<ExplorerUiLinkAddress address={item.address} basePath={basePath} />
36+
</TableCell>
37+
<TableCell className="text-right">
38+
<div className="space-x-2">
39+
<Button
40+
onClick={async () => {
41+
const label = window.prompt('New label?', item.label ?? '')
42+
await updateItem({ ...item, label: label ?? '' })
43+
}}
44+
size="icon"
45+
title="Edit label"
46+
variant="outline"
47+
>
48+
<UiIcon icon="edit" />
49+
</Button>
50+
<UiConfirm
51+
action={async () => {
52+
await deleteItem(item)
53+
}}
54+
actionLabel="Export"
55+
description={'ARE YOU SURE DELETE'}
56+
title={'DELETE BOOKMARK'}
57+
>
58+
<Button size="icon" title="Delete label" variant="outline">
59+
<UiIcon className="text-red-500" icon="delete" />
60+
</Button>
61+
</UiConfirm>
62+
</div>
63+
</TableCell>
64+
</TableRow>
65+
))}
66+
</TableBody>
67+
</Table>
68+
)
69+
}

0 commit comments

Comments
 (0)