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(tangle-dapp): Add Operator Profile Page #2537

Merged
merged 59 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
d10555e
chore(tangle-dapp): Enable restake pages
AtelyPham Aug 28, 2024
1b1cdf1
Merge branch 'develop' into trung-tin/restake-operators-table
AtelyPham Aug 28, 2024
ab1c18a
feat(tangle-dapp): Scaffold restake overview page
AtelyPham Aug 28, 2024
b3f31ad
fix(tangle-dapp): Improve mobile view of restake overview
AtelyPham Aug 28, 2024
8520279
feat(tangle-dapp): Scaffold restake overview tabs
AtelyPham Aug 28, 2024
e35b18c
feat(tangle-dapp): Implement operators table for reusable
AtelyPham Aug 28, 2024
1e00070
feat(tangle-dapp): Adding vaults table
AtelyPham Aug 29, 2024
26c9bc8
feat(tangle-dapp): Integrate data for vaults and operators
AtelyPham Aug 29, 2024
8f82bdb
feat(tangle-dapp): Extract operators table to apply filtering
AtelyPham Aug 29, 2024
8d6fa25
chore(tangle-dapp): Run format
AtelyPham Aug 29, 2024
802af2f
chore: Resolve merge conflict
AtelyPham Aug 29, 2024
140ea03
Merge branch 'develop' into trung-tin/restake-overview-page
AtelyPham Aug 29, 2024
b69df07
fix(tangle-dapp): Fix build command
AtelyPham Aug 29, 2024
001d314
feat: debug Netlify
AtelyPham Aug 29, 2024
3180cad
feat: list hidden files
AtelyPham Aug 29, 2024
0118c34
chore(tangle-dapp): Remove the debug command
AtelyPham Aug 29, 2024
bd6adbf
fix(webb-ui): Resolve the `key` warnings
AtelyPham Aug 30, 2024
98d9e32
feat(tangle-dapp): Add vault tokens dropdown
AtelyPham Aug 30, 2024
6a04d4f
feat(tangle-dapp): Align operator with vault table
AtelyPham Aug 30, 2024
7fa7cb4
feat(tangle-dapp): Integrate apy and self stake for vaults
AtelyPham Aug 30, 2024
8179e76
feat(tangle-dapp): Add proper link to restake tables
AtelyPham Aug 30, 2024
fc9cb3e
Merge branch 'develop' into trung-tin/restake-tvl-calculation
AtelyPham Aug 30, 2024
7e51aa1
feat(tangle-dapp): Add token price fetchers
AtelyPham Sep 1, 2024
2f5fca8
feat(tangle-dapp): Fetch token prices and update asset map
AtelyPham Sep 1, 2024
30a6f71
feat(tangle-dapp): Add useRestakeTVL hook for calculating TVL
AtelyPham Sep 1, 2024
1e4803d
feat(restake): Add OverviewTVLStats component
AtelyPham Sep 1, 2024
441047e
chore(restake): Refactor OperatorsTable component to accept operatorM…
AtelyPham Sep 2, 2024
f70c6e0
feat(tangle-dapp): Calculate pool TVL and display TVLs on UI
AtelyPham Sep 2, 2024
0db1540
chore: Run format
AtelyPham Sep 2, 2024
70926aa
feat(tangle-dapp): Add operator concentration
AtelyPham Sep 2, 2024
587ac43
Merge branch 'develop' into trung-tin/restake-tvl-calculation
AtelyPham Sep 2, 2024
8f82230
chore: Ignore stats on link checker
AtelyPham Sep 2, 2024
b5e4efc
fix(tangle-dapp): add missing query param parsing
AtelyPham Sep 2, 2024
940a311
fix(tangle-dapp): Remove query params after initial load
AtelyPham Sep 3, 2024
4f06c74
chore(tangle-dapp): Update VaultsDropdown component to use div instea…
AtelyPham Sep 3, 2024
d1c551b
chore(restake): Refactor OperatorsTable component to optimize restake…
AtelyPham Sep 3, 2024
c79422d
fix(tangle-dapp): Remove commit links to fix link checker
AtelyPham Sep 3, 2024
95b9f29
Merge branch 'develop' into trung-tin/restake-tvl-calculation
AtelyPham Sep 3, 2024
9d9b474
fix(tangle-dapp): Incoporate base on feedback
AtelyPham Sep 3, 2024
597361c
chore: Remove shortenFn from KeyValueWithButton component
AtelyPham Sep 4, 2024
4d371cb
chore: Add ValidatorSocials component for displaying social media lin…
AtelyPham Sep 4, 2024
4316a8f
refactor(nomination): Update InfoCard component to use ValidatorSocials
AtelyPham Sep 4, 2024
4aa7336
chore: Update Identicon component to use client-side rendering
AtelyPham Sep 4, 2024
111ed46
feat: Add OperatorInfoCard for displaying operator information
AtelyPham Sep 4, 2024
4bf536f
feat: Add RegisteredBlueprintsCard for displaying registered blueprints
AtelyPham Sep 4, 2024
2cdcd3d
Merge branch 'develop' into trung-tin/operator-profile-page
AtelyPham Sep 5, 2024
72e0e43
style(webb-ui): Update ScrollArea component styles
AtelyPham Sep 5, 2024
2790b00
feat(tangle-dapp): Display detail info for registered blueprints
AtelyPham Sep 8, 2024
e8b3f70
feat(tangle-dapp): Add TVL table for operator
AtelyPham Sep 8, 2024
f24a60c
feat(tangle-dapp): Integrate API for operator info card
AtelyPham Sep 8, 2024
5fea510
feat(tangle-dapp): Integrate data for operator TVL table
AtelyPham Sep 8, 2024
3775af0
chore(tangle-dapp): Add TODO for integrate blueprint API
AtelyPham Sep 11, 2024
01bce33
Merge branch 'develop' into trung-tin/operator-profile-page
AtelyPham Sep 12, 2024
1dd4a45
fix(tangle-dapp): Update operator link
AtelyPham Sep 17, 2024
35226ff
chore(tangle-dapp): Allow passing `undefined` operator
AtelyPham Sep 17, 2024
3b832fc
refactor(tangle-dapp): Improve blueprint link structure and accessibi…
AtelyPham Sep 17, 2024
8aa81d8
chore(tangle-dapp): Update components to remove warnings
AtelyPham Sep 17, 2024
fc9a698
Merge branch 'develop' into trung-tin/operator-profile-page
AtelyPham Sep 17, 2024
8fc365f
chore(tangle-dapp): Pass blueprints data outside the UI component
AtelyPham Sep 18, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { IconWithTooltip } from '@webb-tools/webb-ui-components/components/IconW
import { KeyValueWithButton } from '@webb-tools/webb-ui-components/components/KeyValueWithButton';
import { useCopyable } from '@webb-tools/webb-ui-components/hooks/useCopyable';
import type { PropsOf } from '@webb-tools/webb-ui-components/types';
import { shortenString } from '@webb-tools/webb-ui-components/utils/shortenString';
import type { ComponentProps, ElementRef } from 'react';
import { forwardRef } from 'react';
import { twMerge } from 'tailwind-merge';
Expand Down Expand Up @@ -55,7 +54,6 @@ const NoteAccountAvatarWithKey = forwardRef<
.join('')
: keyValue
}
shortenFn={isHiddenValue ? shortenString : undefined}
isDisabledTooltip={isHiddenValue}
copyProps={isHiddenValue ? copyableResult : undefined}
onCopyButtonClick={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import {
formatTokenAmount,
fuzzyFilter,
numberToString,
shortenString,
} from '@webb-tools/webb-ui-components';
import { FC, useMemo } from 'react';

Expand Down Expand Up @@ -121,7 +120,6 @@ const staticColumns = [
cell: (props) => (
<div className="flex items-center">
<KeyValueWithButton
shortenFn={(note: string) => shortenString(note, 4)}
isHiddenLabel
size="sm"
keyValue={props.getValue()}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import {
Chip,
CopyWithTooltip,
ExternalLinkIcon,
SocialChip,
Typography,
} from '@webb-tools/webb-ui-components';
import { shortenString } from '@webb-tools/webb-ui-components/utils/shortenString';
import { FC } from 'react';
import { twMerge } from 'tailwind-merge';

import { TangleCard } from '../../../components';
import ValidatorSocials from '../../../components/ValidatorSocials';
import { EMPTY_VALUE_PLACEHOLDER } from '../../../constants';
import useNetworkStore from '../../../context/useNetworkStore';
import useValidatorInfoCard from '../../../data/validatorDetails/useValidatorInfoCard';
Expand Down Expand Up @@ -106,14 +106,13 @@ const InfoCard: FC<InfoCardProps> = ({
</div>

{/* Socials & Location */}
<div className="flex gap-2 min-h-[30px]">
<div className="flex items-center flex-1 gap-2">
{twitter && <SocialChip type="twitter" href={twitter} />}
{email && <SocialChip type="email" href={`mailto:${email}`} />}
{web && <SocialChip type="website" href={web} />}
</div>
{/* TODO: get location later */}
</div>
<ValidatorSocials
twitterUrl={twitter ?? ''}
email={email ?? ''}
webUrl={web ?? ''}
// TODO: get location later
location={undefined}
/>
</div>
</TangleCard>
);
Expand Down
7 changes: 1 addition & 6 deletions apps/tangle-dapp/app/restake/OperatorList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import { ListCardWrapper } from '@webb-tools/webb-ui-components/components/ListC
import { ListItem } from '@webb-tools/webb-ui-components/components/ListCard/ListItem';
import { ScrollArea } from '@webb-tools/webb-ui-components/components/ScrollArea';
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import { shortenString } from '@webb-tools/webb-ui-components/utils/shortenString';
import isFunction from 'lodash/isFunction';
import keys from 'lodash/keys';
import omitBy from 'lodash/omitBy';
Expand Down Expand Up @@ -110,11 +109,7 @@ const OperatorList = forwardRef<HTMLDivElement, Props>(
operatorIdentities?.[current]?.name || '<Unknown>'
}
description={
<KeyValueWithButton
size="sm"
keyValue={current}
shortenFn={shortenString}
/>
<KeyValueWithButton size="sm" keyValue={current} />
}
/>
</ListItem>
Expand Down
9 changes: 2 additions & 7 deletions apps/tangle-dapp/app/restake/OperatorsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,14 @@ const OperatorsTable: FC<Props> = ({
const operators = useMemo(
() =>
Object.entries(operatorMap).map<OperatorUI>(
([address, { delegations }]) => {
([address, { delegations, restakersCount }]) => {
const vaultAssets = delegations
.map((delegation) => ({
asset: assetMap[delegation.assetId],
amount: delegation.amount,
}))
.filter((vaultAsset) => Boolean(vaultAsset.asset));

const restakerSet = delegations.reduce((restakerSet, delegation) => {
restakerSet.add(delegation.delegatorAccountId);
return restakerSet;
}, new Set<string>());

const tvlInUsd = operatorTVL?.[address] ?? null;
const concentrationPercentage =
operatorConcentration?.[address] ?? null;
Expand All @@ -57,7 +52,7 @@ const OperatorsTable: FC<Props> = ({
address,
concentrationPercentage,
identityName: identities[address]?.name ?? '',
restakersCount: restakerSet.size,
restakersCount,
tvlInUsd,
vaultTokens: vaultAssets.map(({ asset, amount }) => ({
amount: +formatUnits(amount, asset.decimals),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import { isHex } from '@polkadot/util';
import isValidUrl from '@webb-tools/dapp-types/utils/isValidUrl';
import { ExternalLinkLine } from '@webb-tools/icons/ExternalLinkLine';
import { Chip } from '@webb-tools/webb-ui-components/components/Chip';
import InfoIconWithTooltip from '@webb-tools/webb-ui-components/components/IconWithTooltip/InfoIconWithTooltip';
import { KeyValueWithButton } from '@webb-tools/webb-ui-components/components/KeyValueWithButton';
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import { shortenHex } from '@webb-tools/webb-ui-components/utils/shortenHex';
import { shortenString } from '@webb-tools/webb-ui-components/utils/shortenString';
import { type ComponentProps, type FC, type ReactNode, useMemo } from 'react';
import useSWRImmutable from 'swr/immutable';
import { twMerge } from 'tailwind-merge';

import GlassCard from '../../../../components/GlassCard/GlassCard';
import ValidatorSocials from '../../../../components/ValidatorSocials';
import { EMPTY_VALUE_PLACEHOLDER } from '../../../../constants';
import useNetworkStore from '../../../../context/useNetworkStore';
import type {
DelegatorInfo,
OperatorMap,
OperatorMetadata,
} from '../../../../types/restake';
import getTVLToDisplay from '../../../../utils/getTVLToDisplay';
import { getAccountInfo } from '../../../../utils/polkadot';
import AvatarWithText from '../../AvatarWithText';

interface Props extends Partial<ComponentProps<typeof GlassCard>> {
operatorAddress: string;
operatorData: OperatorMetadata | undefined;
operatorMap: OperatorMap;
delegatorInfo: DelegatorInfo | null;
operatorTVL: Record<string, number>;
}

const OperatorInfoCard: FC<Props> = ({
className,
operatorAddress,
operatorData,
operatorMap,
delegatorInfo,
operatorTVL,
...props
}) => {
const { rpcEndpoint } = useNetworkStore();

const isRestaked = useMemo<boolean>(() => {
if (delegatorInfo === null) {
return false;
}

const foundDelegation = delegatorInfo.delegations.find(
(delegation) => delegation.operatorAccountId === operatorAddress,
);

return Boolean(foundDelegation);
}, [delegatorInfo, operatorAddress]);

const totalRestaked = useMemo(
() => getTVLToDisplay(operatorTVL[operatorAddress]),
[operatorAddress, operatorTVL],
);

const restakersCount = useMemo(
() => operatorData?.restakersCount.toString() ?? EMPTY_VALUE_PLACEHOLDER,
[operatorData?.restakersCount],
);

const { data: operatorInfo } = useSWRImmutable(
[rpcEndpoint, operatorAddress],
(args) => getAccountInfo(...args),
);

const identityName = useMemo(() => {
const defaultName = isHex(operatorAddress)
? shortenHex(operatorAddress)
: shortenString(operatorAddress);

if (!operatorInfo) {
return defaultName;
}

return operatorInfo.name || defaultName;
}, [operatorAddress, operatorInfo]);

const validatorSocials = useMemo(() => {
const twitterHandle = operatorInfo?.twitter ?? '';
const webUrl = operatorInfo?.web ?? '';
const email = operatorInfo?.email ?? '';

const twitterUrl =
twitterHandle === '' || isValidUrl(twitterHandle)
? twitterHandle
: `https://x.com/${twitterHandle}`;

return {
twitterUrl,
webUrl,
email,
// TODO: Add location
location: '',
// TODO: Add github link
githubUrl: '',
};
}, [operatorInfo?.email, operatorInfo?.twitter, operatorInfo?.web]);

return (
<GlassCard {...props} className={twMerge('gap-10', className)}>
<div className="flex items-start justify-between">
<AvatarWithText
overrideAvatarProps={{
size: 'lg',
}}
overrideTypographyProps={{
variant: 'h4',
fw: 'bold',
}}
accountAddress={operatorAddress}
identityName={identityName}
description={
<div className="flex items-baseline gap-1">
<KeyValueWithButton
className="mt-1"
size="sm"
keyValue={operatorAddress}
/>

<ExternalLinkLine />
</div>
}
/>

{isRestaked && <Chip color="green">Restaked</Chip>}
</div>

<div className="flex flex-wrap gap-4">
<StatsItem label="Total Restake" value={totalRestaked} />
<StatsItem label="Restakers" value={restakersCount} />
</div>

<ValidatorSocials {...validatorSocials} />
</GlassCard>
);
};

export default OperatorInfoCard;

interface StatsItemProps {
label: string;
value: string;
info?: ReactNode;
}

const StatsItem: FC<StatsItemProps> = ({ label, value, info }) => {
return (
<div className="flex-1">
<Typography variant="h4" fw="bold">
{value}
</Typography>

<Typography
variant="h5"
fw="normal"
className="text-mono-120 dark:text-mono-100"
>
{label}
{info && (
<InfoIconWithTooltip
className="fill-mono-120 dark:fill-mono-100"
content={info}
/>
)}
</Typography>
</div>
);
};
Loading
Loading