From bc5fabeb924de734391cab6221e72650571705a7 Mon Sep 17 00:00:00 2001 From: Max Korsunov Date: Fri, 10 Jan 2025 10:12:26 +0400 Subject: [PATCH] feat(extension): #252: asset list (#259) * feat(extension): #252: add assets table to the main popup screen * chore: changeset * fix: #252: fix review comments * fix: possible height fix --- .changeset/strong-carrots-protect.md | 6 ++ .../src/routes/popup/home/assets-table.tsx | 88 +++++++++++++++++++ .../extension/src/routes/popup/home/index.tsx | 38 +++++--- .../src/routes/popup/popup-layout.tsx | 2 +- packages/tailwind-config/index.ts | 4 +- .../components/ui/select/select-account.tsx | 5 +- packages/ui/components/ui/table/index.tsx | 81 +++++++++++++++++ 7 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 .changeset/strong-carrots-protect.md create mode 100644 apps/extension/src/routes/popup/home/assets-table.tsx create mode 100644 packages/ui/components/ui/table/index.tsx diff --git a/.changeset/strong-carrots-protect.md b/.changeset/strong-carrots-protect.md new file mode 100644 index 00000000..68deabff --- /dev/null +++ b/.changeset/strong-carrots-protect.md @@ -0,0 +1,6 @@ +--- +'chrome-extension': minor +'@repo/ui': minor +--- + +Add AssetsTable to the home screen of the extension diff --git a/apps/extension/src/routes/popup/home/assets-table.tsx b/apps/extension/src/routes/popup/home/assets-table.tsx new file mode 100644 index 00000000..e28173b4 --- /dev/null +++ b/apps/extension/src/routes/popup/home/assets-table.tsx @@ -0,0 +1,88 @@ +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from '@repo/ui/components/ui/table'; +import { ValueViewComponent } from '@repo/ui/components/ui/value'; +import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { getDisplayDenomFromView, getEquivalentValues } from '@penumbra-zone/getters/value-view'; +import { getMetadataFromBalancesResponse } from '@penumbra-zone/getters/balances-response'; +import { asValueView } from '@penumbra-zone/getters/equivalent-value'; +import { useQuery } from '@tanstack/react-query'; +import { viewClient } from '../../../clients'; + +const EquivalentValues = ({ valueView }: { valueView?: ValueView }) => { + const equivalentValuesAsValueViews = (getEquivalentValues.optional(valueView) ?? []).map( + asValueView, + ); + + return ( +
+ {equivalentValuesAsValueViews.map(equivalentValueAsValueView => ( + + ))} +
+ ); +}; + +export interface AssetsTableProps { + account: number; +} + +export const AssetsTable = ({ account }: AssetsTableProps) => { + const { + data: balances, + isLoading, + error, + } = useQuery({ + queryKey: ['balances', account], + staleTime: Infinity, + queryFn: async () => { + try { + const balances = await Array.fromAsync(viewClient.balances({ accountFilter: { account } })); + balances.sort((a, b) => { + const aScore = getMetadataFromBalancesResponse.optional(a)?.priorityScore ?? 0n; + const bScore = getMetadataFromBalancesResponse.optional(b)?.priorityScore ?? 0n; + return Number(bScore - aScore); + }); + return balances; + } catch (_) { + return []; + } + }, + }); + + if (isLoading || error || !balances?.length) { + return null; + } + + return ( + + + + Balance + Value + + + + {balances.map((assetBalance, index) => ( + + + + + + + + + ))} + +
+ ); +}; diff --git a/apps/extension/src/routes/popup/home/index.tsx b/apps/extension/src/routes/popup/home/index.tsx index 392e22d9..4f18ccf0 100644 --- a/apps/extension/src/routes/popup/home/index.tsx +++ b/apps/extension/src/routes/popup/home/index.tsx @@ -1,15 +1,17 @@ +import { Address, FullViewingKey } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; import { SelectAccount } from '@repo/ui/components/ui/select'; +import { getAddressByIndex, getEphemeralByIndex } from '@penumbra-zone/wasm/keys'; +import { Wallet } from '@penumbra-zone/types/wallet'; import { IndexHeader } from './index-header'; import { useStore } from '../../../state'; import { BlockSync } from './block-sync'; import { localExtStorage } from '../../../storage/local'; import { getActiveWallet } from '../../../state/wallets'; import { needsLogin, needsOnboard } from '../popup-needs'; -import { Address, FullViewingKey } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; -import { getAddressByIndex, getEphemeralByIndex } from '@penumbra-zone/wasm/keys'; -import { Wallet } from '@penumbra-zone/types/wallet'; import { ValidateAddress } from './validate-address'; import { FrontendLink } from './frontend-link'; +import { AssetsTable } from './assets-table'; +import { useState } from 'react'; export interface PopupLoaderData { fullSyncHeight?: number; @@ -45,23 +47,35 @@ const getAddrByIndex = export const PopupIndex = () => { const activeWallet = useStore(getActiveWallet); + const [index, setIndex] = useState(0); return ( <> - +
+
-
- +
+ -
- {activeWallet && } -
+
+ - +
+ {activeWallet && ( + + )} +
-
+ - + + + +
); diff --git a/apps/extension/src/routes/popup/popup-layout.tsx b/apps/extension/src/routes/popup/popup-layout.tsx index 1d609adb..12a222a3 100644 --- a/apps/extension/src/routes/popup/popup-layout.tsx +++ b/apps/extension/src/routes/popup/popup-layout.tsx @@ -16,7 +16,7 @@ export const PopupLayout = () => { usePopupReady(); return ( -
+
); diff --git a/packages/tailwind-config/index.ts b/packages/tailwind-config/index.ts index 1a7e6658..71483f6a 100644 --- a/packages/tailwind-config/index.ts +++ b/packages/tailwind-config/index.ts @@ -151,9 +151,9 @@ export default { linear-gradient( color-mix(in srgb, var(--charcoal) 80%, transparent), color-mix(in srgb, var(--charcoal) 80%, transparent) - ), - url('penumbra-logo.svg') + ) `, + logoImg: `url('penumbra-logo.svg')`, }, }, }, diff --git a/packages/ui/components/ui/select/select-account.tsx b/packages/ui/components/ui/select/select-account.tsx index 245572dd..2f058290 100644 --- a/packages/ui/components/ui/select/select-account.tsx +++ b/packages/ui/components/ui/select/select-account.tsx @@ -13,14 +13,15 @@ import { Box } from '../box'; export interface SelectAccountProps { getAddrByIndex: (index: number, ephemeral: boolean) => Promise
| Address; + index: number; + setIndex: (index: number) => void; } /** * Renders an account address, along with a switcher to choose a different * account index. Also allows the user to view a one-time IBC deposit address. */ -export const SelectAccount = ({ getAddrByIndex }: SelectAccountProps) => { - const [index, setIndex] = useState(0); +export const SelectAccount = ({ getAddrByIndex, index, setIndex }: SelectAccountProps) => { const [ephemeral, setEphemeral] = useState(false); const [address, setAddress] = useState
(); diff --git a/packages/ui/components/ui/table/index.tsx b/packages/ui/components/ui/table/index.tsx new file mode 100644 index 00000000..72ddd187 --- /dev/null +++ b/packages/ui/components/ui/table/index.tsx @@ -0,0 +1,81 @@ +import * as React from 'react'; +import { cn } from '../../../lib/utils'; + +const Table = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +Table.displayName = 'Table'; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = 'TableHeader'; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = 'TableBody'; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableFooter.displayName = 'TableFooter'; + +const TableRow = React.forwardRef>( + ({ className, ...props }, ref) => ( + + ), +); +TableRow.displayName = 'TableRow'; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = 'TableHead'; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = 'TableCell'; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = 'TableCaption'; + +export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };