Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(extension): #252: asset list #259

Merged
merged 4 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/strong-carrots-protect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'chrome-extension': minor
'@repo/ui': minor
---

Add AssetsTable to the home screen of the extension
88 changes: 88 additions & 0 deletions apps/extension/src/routes/popup/home/assets-table.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className='flex flex-wrap gap-2'>
{equivalentValuesAsValueViews.map(equivalentValueAsValueView => (
<ValueViewComponent
key={getDisplayDenomFromView(equivalentValueAsValueView)}
view={equivalentValueAsValueView}
variant='equivalent'
/>
))}
</div>
);
};

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 (
<Table>
<TableHeader className='group'>
<TableRow>
<TableHead>Balance</TableHead>
Copy link
Contributor

@TalDerei TalDerei Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: can we make UM (and optionally the fee tokens – USDC, OSMO, ATOM) the priority by ordering them higher in the balance list?

<TableHead>Value</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{balances.map((assetBalance, index) => (
<TableRow className='group' key={index}>
<TableCell>
<ValueViewComponent view={assetBalance.balanceView} />
</TableCell>
<TableCell>
<EquivalentValues valueView={assetBalance.balanceView} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};
38 changes: 26 additions & 12 deletions apps/extension/src/routes/popup/home/index.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -45,23 +47,35 @@ const getAddrByIndex =

export const PopupIndex = () => {
const activeWallet = useStore(getActiveWallet);
const [index, setIndex] = useState<number>(0);

return (
<>
<BlockSync />
<div className='fixed inset-0 h-full bg-logoImg bg-[left_-180px] bg-no-repeat pointer-events-none' />
<div className='fixed inset-0 h-full bg-logo pointer-events-none' />
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit; there's visible discoloration at the bottom of the extension window where the gradient ends. can we add some kind of smooth fading or modify the transparency?

Screenshot 2025-01-09 at 7 12 05 AM

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's so weird, i can't reproduce

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did a patch fix, no idea if it helps, but at least not breaks anything. Can you create a new issue if the bug persists, so somebody who can reproduce it can fix?

Copy link
Contributor

@TalDerei TalDerei Jan 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not necessarily blocking, feel free to merge – filed #264

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks @TalDerei

merging this now


<div className='flex h-full grow flex-col items-stretch gap-[15px] bg-logo bg-left-bottom px-[15px] pb-[15px]'>
<IndexHeader />
<div className='z-[1] flex flex-col h-full'>
<BlockSync />

<div className='flex flex-col gap-4'>
{activeWallet && <SelectAccount getAddrByIndex={getAddrByIndex(activeWallet)} />}
</div>
<div className='flex h-full grow flex-col items-stretch gap-[15px] px-[15px] pb-[15px]'>
<IndexHeader />

<ValidateAddress />
<div className='flex flex-col gap-4'>
{activeWallet && (
<SelectAccount
index={index}
setIndex={setIndex}
getAddrByIndex={getAddrByIndex(activeWallet)}
/>
)}
</div>

<div className='shrink-0 grow' />
Copy link
Contributor

@TalDerei TalDerei Jan 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment: removing this may be causing the layout issues when scrolling? the position should be fixed to prevent scrolling outside the bounds of the extension

<ValidateAddress />

<FrontendLink />
<FrontendLink />

<AssetsTable account={index} />
</div>
</div>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/extension/src/routes/popup/popup-layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const PopupLayout = () => {
usePopupReady();

return (
<div className='flex grow flex-col bg-card-radial'>
<div className='relative flex grow flex-col bg-card-radial'>
<Outlet />
</div>
);
Expand Down
4 changes: 2 additions & 2 deletions packages/tailwind-config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')`,
},
},
},
Expand Down
5 changes: 3 additions & 2 deletions packages/ui/components/ui/select/select-account.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,15 @@ import { Box } from '../box';

export interface SelectAccountProps {
getAddrByIndex: (index: number, ephemeral: boolean) => Promise<Address> | 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<number>(0);
export const SelectAccount = ({ getAddrByIndex, index, setIndex }: SelectAccountProps) => {
const [ephemeral, setEphemeral] = useState<boolean>(false);
const [address, setAddress] = useState<Address>();

Expand Down
81 changes: 81 additions & 0 deletions packages/ui/components/ui/table/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import * as React from 'react';
import { cn } from '../../../lib/utils';

const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
({ className, ...props }, ref) => (
<table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
),
);
Table.displayName = 'Table';

const TableHeader = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';

const TableBody = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
));
TableBody.displayName = 'TableBody';

const TableFooter = React.forwardRef<
HTMLTableSectionElement,
React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
<tfoot
ref={ref}
className={cn('bg-primary font-medium text-primary-foreground', className)}
{...props}
/>
));
TableFooter.displayName = 'TableFooter';

const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
({ className, ...props }, ref) => (
<tr ref={ref} className={cn('border-b border-border-secondary', className)} {...props} />
),
);
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<
HTMLTableCellElement,
React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<th
ref={ref}
className={cn(
'py-4 text-left align-top text-lg leading-[26px] font-headline font-semibold [&:has([role=checkbox])]:pr-0',
className,
)}
{...props}
/>
));
TableHead.displayName = 'TableHead';

const TableCell = React.forwardRef<
HTMLTableCellElement,
React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
<td
ref={ref}
className={cn('py-4 align-middle text-base [&:has([role=checkbox])]:pr-0', className)}
{...props}
/>
));
TableCell.displayName = 'TableCell';

const TableCaption = React.forwardRef<
HTMLTableCaptionElement,
React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
<caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
));
TableCaption.displayName = 'TableCaption';

export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };