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(explorer): account page #2238

Merged
merged 12 commits into from
Jun 6, 2024
5 changes: 5 additions & 0 deletions .changeset/fluffy-kings-battle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@kadena/explorer": minor
---

add the account page
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import {
Cell,
Column,
Row,
Table,
TableBody,
TableHeader,
} from '@kadena/react-ui';
import type { FC } from 'react';
import React from 'react';
import type { ICompactKeyTableProps } from '../compact-keys-table';
import { tableClass } from './styles.css';

const CompactTransactionsTableDesktop: FC<ICompactKeyTableProps> = ({
keys,
}) => {
return (
<Table aria-label="Keys table" isStriped className={tableClass}>
<TableHeader>
<Column width="10%">ChainId</Column>
<Column width="50%">Key</Column>
<Column width="40%">Predicate</Column>
</TableHeader>
<TableBody>
{keys.map((key) => (
<Row key={key.key + key.chainId}>
<Cell>
<span>{key.chainId}</span>
</Cell>
<Cell>
<span>{key.key}</span>
</Cell>
<Cell>
<span>{key.predicate}</span>
</Cell>
</Row>
))}
</TableBody>
</Table>
);
};
export default CompactTransactionsTableDesktop;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { globalStyle, style } from '@vanilla-extract/css';

export const tableClass = style({
width: '100%',
});

globalStyle(`${tableClass} td span`, {
display: 'block',
alignItems: 'center',
contain: 'inline-size',
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {
dataFieldClass,
dataFieldLinkClass,
headerClass,
rowClass,
sectionClass,
} from '@/components/compact-transactions-table/compact-transactions-table-mobile/styles.css';
import { Text } from '@kadena/react-ui';
import type { FC } from 'react';
import React from 'react';
import type { ICompactKeyTableProps } from '../compact-keys-table';

const CompactTransactionsTableMobile: FC<ICompactKeyTableProps> = ({
keys,
}) => {
return keys.map((key, index) => (
<section key={index} className={sectionClass}>
<div className={rowClass}>
<span className={headerClass}>ChainId</span>
{key.chainId}
</div>
<div className={rowClass}>
<span className={headerClass}>Key</span>
<Text variant="code" className={dataFieldClass}>
{key.key}
</Text>
</div>
<div className={rowClass}>
<span className={headerClass}>Predicate</span>
<span className={dataFieldLinkClass}>
<Text variant="code" className={dataFieldClass}>
{key.predicate}
</Text>
</span>
</div>
</section>
));
};
export default CompactTransactionsTableMobile;
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Media } from '@/components/layout/media';
import type { FC } from 'react';
import React from 'react';
import CompactKeysTableDesktop from './compact-keys-table-desktop/compact-keys-table-desktop';
import CompactKeysTableMobile from './compact-keys-table-mobile/compact-keys-table-mobile';

export interface IKeyProps {
chainId: string;
key: string;
predicate: string;
}

export interface ICompactKeyTableProps {
keys: IKeyProps[];
}

const CompactKeysTable: FC<ICompactKeyTableProps> = ({ keys }) => {
return (
<>
<Media lessThan="sm">
<CompactKeysTableMobile keys={keys} />
</Media>
<Media greaterThanOrEqual="sm">
<CompactKeysTableDesktop keys={keys} />
</Media>
</>
);
};
export default CompactKeysTable;
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,32 @@ import type {
Transaction,
TransactionResult,
} from '@/__generated__/sdk';
import { tableClass } from '@/components/compact-keys-table/compact-keys-table-desktop/styles.css';
import {
MonoArrowOutward,
MonoCheck,
MonoClear,
} from '@kadena/react-icons/system';
import { Badge, Text } from '@kadena/react-ui';
import React, { Fragment } from 'react';
import {
Badge,
Cell,
Column,
Row,
Stack,
Table,
TableBody,
TableHeader,
Text,
} from '@kadena/react-ui';
import Link from 'next/link';
import React from 'react';
import {
badgeClass,
dataFieldClass,
dataFieldLinkClass,
headerClass,
iconLinkClass,
linkClass,
linkIconClass,
sectionClass,
linkWrapperClass,
} from './styles.css';

interface ICompactTransactionsTableDesktopProps {
Expand All @@ -29,44 +39,61 @@ const CompactTransactionsTableDesktop: React.FC<
ICompactTransactionsTableDesktopProps
> = ({ transactions }) => {
return (
<section className={sectionClass}>
<span>
<Badge className={badgeClass} size="sm">
Status
</Badge>
</span>
<span className={headerClass}>Sender</span>
<span className={headerClass}>Request Key</span>
<span className={headerClass}>Code Preview</span>
{transactions.map((transaction, index) => (
<Fragment key={index}>
{(transaction.result as TransactionResult).goodResult ? (
<MonoCheck />
) : (
<MonoClear />
)}
<Text variant="code" className={dataFieldClass}>
{transaction.cmd.meta.sender}
</Text>
<span className={dataFieldLinkClass}>
<a href={`/transaction/${transaction.hash}`} className={linkClass}>
<Text variant="code" className={dataFieldClass}>
{transaction.hash}
<Table aria-label="Keys table" isStriped className={tableClass}>
<TableHeader>
<Column width="10%">
<Badge className={badgeClass} size="sm">
Status
</Badge>
</Column>
<Column width="25%">Sender</Column>
<Column width="25%">Request Key</Column>
<Column width="40%">Code Preview</Column>
</TableHeader>
<TableBody>
{transactions.map((transaction) => (
<Row key={transaction.id}>
<Cell>
{(transaction.result as TransactionResult).goodResult ? (
<MonoCheck />
) : (
<MonoClear />
)}
</Cell>
<Cell>
<Text as="span" variant="code" className={dataFieldClass}>
{transaction.cmd.meta.sender}
</Text>
</Cell>
<Cell>
<span>
<Stack alignItems="center" className={linkWrapperClass}>
<Link
href={`/transaction/${transaction.hash}`}
className={linkClass}
>
<Text variant="code" className={dataFieldClass}>
{transaction.hash}
</Text>
</Link>
<Link
href={`/transaction/${transaction.hash}`}
className={iconLinkClass}
>
<MonoArrowOutward className={linkIconClass} />
</Link>
</Stack>
</span>
</Cell>
<Cell>
<Text as="span" variant="code" className={dataFieldClass}>
{(transaction.cmd.payload as ExecutionPayload).code}
</Text>
</a>
<a
href={`/transaction/${transaction.hash}`}
className={iconLinkClass}
>
<MonoArrowOutward className={linkIconClass} />
</a>
</span>
<Text variant="code" className={dataFieldClass}>
{(transaction.cmd.payload as ExecutionPayload).code}
</Text>
</Fragment>
))}
</section>
</Cell>
</Row>
))}
</TableBody>
</Table>
);
};
export default CompactTransactionsTableDesktop;
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const linkClass = style({
textDecoration: 'none',
});

export const linkWrapperClass = style({
width: `calc(100% - 15px)`,
});
export const iconLinkClass = style([
atoms({
color: 'text.base.default',
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { maskValue } from '@kadena/react-ui';
import type { FC } from 'react';
import React from 'react';

interface IProps {
value?: string;
}

const MaskedAccountName: FC<IProps> = ({ value }) => {
if (!value) return null;

return <span>{maskValue(value)}</span>;
};

export default MaskedAccountName;
25 changes: 25 additions & 0 deletions packages/apps/explorer/src/graphql/queries/account.graph.ts
Copy link
Member

Choose a reason for hiding this comment

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

We should probably check what fields we select, and what we don't need, exclude.

I'd rather have a query per page, so we load specifically the data that's needed for that page and the features on that page.

Let's discuss in a refinement.

For now, let's do it like it is

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { gql } from '@apollo/client';
import type { DocumentNode } from 'graphql';

export const block: DocumentNode = gql`
query account($accountName: String!) {
fungibleAccount(accountName: $accountName) {
accountName
totalBalance
chainAccounts {
chainId
guard {
keys
predicate
}
}
transactions {
edges {
node {
...CoreTransactionFields
}
}
}
}
}
`;
76 changes: 76 additions & 0 deletions packages/apps/explorer/src/pages/account/[accountName].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Transaction } from '@/__generated__/sdk';
import { useAccountQuery } from '@/__generated__/sdk';
import CompactKeysTable from '@/components/compact-keys-table/compact-keys-table';
import type { IKeyProps } from '@/components/compact-keys-table/types';
import CompactTransactionsTable from '@/components/compact-transactions-table/compact-transactions-table';
import MaskedAccountName from '@/components/mask-accountname/mask-accountname';
import { Heading, Stack } from '@kadena/react-ui';
import { useRouter } from 'next/router';
import type { FC } from 'react';
import React, { useMemo } from 'react';

const Account: FC = () => {
const router = useRouter();

const { loading, data, error } = useAccountQuery({
variables: {
accountName: router.query.accountName as string,
},
skip: !router.query.accountName,
});

const { fungibleAccount } = data ?? {};

const keys: IKeyProps[] = useMemo(() => {
const innerKeys: IKeyProps[] =
fungibleAccount?.chainAccounts.reduce((acc: IKeyProps[], val) => {
const guardKeys: IKeyProps[] = val.guard.keys.map((key) => {
return {
key: key,
predicate: val.guard.predicate,
chainId: val.chainId,
};
});
return [...acc, ...guardKeys];
}, []) ?? [];

return innerKeys.sort((a: IKeyProps, b: IKeyProps) => {
if (parseInt(a.chainId, 10) < parseInt(b.chainId, 10)) return -1;
if (parseInt(a.chainId, 10) > parseInt(b.chainId, 10)) return 1;
return 0;
});
}, [fungibleAccount?.chainAccounts]);

return (
<>
{loading && <div>Loading...</div>}
{error && <div>Error: {error.message}</div>}

<Stack margin="md">
<Heading as="h5">
{parseFloat(fungibleAccount?.totalBalance).toFixed(2)} KDA spread
across {fungibleAccount?.chainAccounts.length} Chains for account{' '}
<MaskedAccountName value={fungibleAccount?.accountName ?? ''} />
</Heading>
</Stack>
{keys && <CompactKeysTable keys={keys} />}
<Stack
width="100%"
gap="md"
flexDirection={{ xs: 'column-reverse', md: 'row' }}
>
<Stack flex={1} flexDirection="column">
{fungibleAccount?.transactions && (
<CompactTransactionsTable
transactions={fungibleAccount?.transactions.edges.map(
(edge) => edge.node as Transaction,
)}
/>
)}
</Stack>
</Stack>
</>
);
};

export default Account;
Loading