Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const GLASS_CONTAINER_CLASS =

export const TangleCloudTable = <T extends RowData>({
title,
hideTitle = false,
data,
isLoading,
error,
Expand All @@ -28,17 +29,18 @@ export const TangleCloudTable = <T extends RowData>({
? `${errorMessage.slice(0, 137)}...`
: errorMessage
: 'Indexer or network request failed.';
const hasTitle = !hideTitle;

const titleElement = (
const titleElement = hasTitle ? (
<Typography variant="h5" fw="bold">
{title}
</Typography>
);
) : undefined;

if (isLoading) {
return (
<div className={GLASS_CONTAINER_CLASS}>
{titleElement}
{hasTitle ? titleElement : null}

<TableStatus
{...loadingTableProps}
Expand All @@ -49,7 +51,8 @@ export const TangleCloudTable = <T extends RowData>({
}
icon={loadingTableProps?.icon ?? '🔄'}
className={twMerge(
'w-full !border-0 !rounded-none !p-0 mt-3',
'w-full !border-0 !rounded-none !p-0',
hasTitle ? 'mt-3' : null,
loadingTableProps?.className,
)}
/>
Expand All @@ -60,7 +63,7 @@ export const TangleCloudTable = <T extends RowData>({
if (error) {
return (
<div className={GLASS_CONTAINER_CLASS}>
{titleElement}
{hasTitle ? titleElement : null}

<TableStatus
{...errorTableProps}
Expand All @@ -78,7 +81,8 @@ export const TangleCloudTable = <T extends RowData>({
(hasRetry ? { onClick: () => void onRetry() } : undefined)
}
className={twMerge(
'w-full !border-0 !rounded-none !p-0 mt-3',
'w-full !border-0 !rounded-none !p-0',
hasTitle ? 'mt-3' : null,
errorTableProps?.className,
)}
/>
Expand All @@ -89,15 +93,16 @@ export const TangleCloudTable = <T extends RowData>({
if (isEmpty) {
return (
<div className={GLASS_CONTAINER_CLASS}>
{titleElement}
{hasTitle ? titleElement : null}

<TableStatus
{...emptyTableProps}
title={emptyTableProps?.title ?? 'Empty Table'}
description={emptyTableProps?.description ?? 'No data found'}
icon={emptyTableProps?.icon ?? '🔍'}
className={twMerge(
'w-full !border-0 !rounded-none !p-0 mt-3',
'w-full !border-0 !rounded-none !p-0',
hasTitle ? 'mt-3' : null,
emptyTableProps?.className,
)}
/>
Expand All @@ -107,19 +112,22 @@ export const TangleCloudTable = <T extends RowData>({

return (
<Table
title={title}
title={hasTitle ? title : ''}
variant={TableVariant.GLASS_OUTER}
isPaginated
{...tableConfig}
tableProps={tableProps as any}
className={twMerge('w-full px-6 pt-4', tableConfig?.className)}
className={twMerge(
hasTitle ? 'w-full px-6 pt-4' : 'w-full px-6 pt-0',
tableConfig?.className,
)}
tableClassName={tableConfig?.tableClassName}
thClassName={tableConfig?.thClassName}
tbodyClassName={tableConfig?.tbodyClassName}
trClassName={twMerge('group overflow-hidden', tableConfig?.trClassName)}
tdClassName={twMerge('!p-3 max-w-xs', tableConfig?.tdClassName)}
paginationClassName={tableConfig?.paginationClassName}
titleElement={titleElement}
titleElement={hasTitle ? titleElement : undefined}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { RowData, type useReactTable } from '@tanstack/react-table';

export interface TangleCloudTableProps<T extends RowData> {
title: string;
hideTitle?: boolean;
data: T[];
isLoading: boolean;
error: Error | null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
SlashDisputeEligibility,
SlashProposal,
} from '@tangle-network/tangle-shared-ui/data/graphql';
import { Card, Typography } from '@tangle-network/ui-components';
import { formatDateTime, formatTimeRemaining } from '../utils';

interface SlashingSummaryCardsProps {
activeRegistrationsCount: number;
activeAgainstMeCount: number;
myActiveProposalCount: number;
nearestPendingSlash: SlashProposal | null;
nearestPendingSlashEligibility: SlashDisputeEligibility | null;
}

const SlashingSummaryCards = ({
activeRegistrationsCount,
activeAgainstMeCount,
myActiveProposalCount,
nearestPendingSlash,
nearestPendingSlashEligibility,
}: SlashingSummaryCardsProps) => {
return (
<>
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
<Card className="p-4">
<Typography variant="body2" className="text-mono-100">
Active Registrations
</Typography>
<Typography variant="h4" className="mt-1">
{activeRegistrationsCount}
</Typography>
</Card>

<Card className="p-4">
<Typography variant="body2" className="text-mono-100">
Active Against You
</Typography>
<Typography variant="h4" className="mt-1">
{activeAgainstMeCount}
</Typography>
</Card>

<Card className="p-4">
<Typography variant="body2" className="text-mono-100">
My Active Proposals
</Typography>
<Typography variant="h4" className="mt-1">
{myActiveProposalCount}
</Typography>
</Card>
</div>

{nearestPendingSlash && nearestPendingSlashEligibility ? (
<Card className="p-4 !border-yellow-400/30 !bg-yellow-500/15">
<div className="flex items-center gap-2 mb-2">
<div className="w-2 h-2 rounded-full bg-yellow-400 animate-pulse" />
<Typography variant="body2" fw="bold" className="text-yellow-400">
Nearest Dispute Deadline Against You
</Typography>
</div>
<Typography variant="h5" fw="bold" className="text-mono-0">
Slash #{nearestPendingSlash.id.toString()}{' '}
<span className="font-normal text-mono-100">expires at</span>{' '}
{formatDateTime(nearestPendingSlash.executeAfter)}
</Typography>
<Typography
variant="body2"
fw="semibold"
className="text-yellow-300/80 mt-1.5"
>
Time remaining:{' '}
{formatTimeRemaining(
nearestPendingSlashEligibility.secondsUntilDeadline,
)}
</Typography>
</Card>
) : null}
</>
);
};

export default SlashingSummaryCards;
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { SlashProposal } from '@tangle-network/tangle-shared-ui/data/graphql';
import {
Button,
Card,
TabContent,
Typography,
} from '@tangle-network/ui-components';
import { TableAndChartTabs } from '@tangle-network/ui-components/components/TableAndChartTabs';
import { useReactTable } from '@tanstack/react-table';
import { TangleCloudTable } from '../../../../components/tangleCloudTable/TangleCloudTable';
import { SLASHING_TAB_ICONS, SLASHING_TABS, SlashingTab } from '../constants';

interface SlashingTabsTableProps {
selectedSlashingTab: SlashingTab;
onSlashingTabChange: (tab: SlashingTab) => void;
onOpenProposeModal: () => void;
myProposals: SlashProposal[];
againstMe: SlashProposal[];
loadingSlash: boolean;
slashError: Error | null;
refetchSlashProposals: () => void;
myProposalsTable: ReturnType<typeof useReactTable<SlashProposal>>;
againstMeTable: ReturnType<typeof useReactTable<SlashProposal>>;
executeError: string | null;
onDismissExecuteError: () => void;
}

const SlashingTabsTable = ({
selectedSlashingTab,
onSlashingTabChange,
onOpenProposeModal,
myProposals,
againstMe,
loadingSlash,
slashError,
refetchSlashProposals,
myProposalsTable,
againstMeTable,
executeError,
onDismissExecuteError,
}: SlashingTabsTableProps) => {
return (
<Card className="p-4 space-y-4">
<div className="flex items-center justify-between gap-3">
<Typography variant="h5" fw="bold">
Slashing Management
</Typography>
<Button variant="secondary" onClick={onOpenProposeModal}>
New Proposal
</Button>
</div>

{executeError ? (
<Card className="p-3 border border-red-500/30 bg-red-500/10">
<div className="flex items-center justify-between gap-3">
<Typography
variant="body2"
className="text-red-70 dark:text-red-50"
>
{executeError}
</Typography>
<Button variant="utility" size="sm" onClick={onDismissExecuteError}>
Dismiss
</Button>
</div>
</Card>
) : null}

<TableAndChartTabs
tabs={SLASHING_TABS}
icons={SLASHING_TAB_ICONS}
value={selectedSlashingTab}
onValueChange={(tab) => onSlashingTabChange(tab as SlashingTab)}
className="space-y-5"
enableAdvancedDivider
>
<TabContent value={SlashingTab.MY_PROPOSALS}>
<TangleCloudTable
title="Proposals Created By You"
hideTitle
data={myProposals}
isLoading={loadingSlash}
error={slashError}
onRetry={refetchSlashProposals}
tableProps={myProposalsTable}
tableConfig={{
tdClassName: '!px-4 !py-3.5 max-w-none whitespace-nowrap',
thClassName: 'whitespace-nowrap',
}}
loadingTableProps={{
title: 'Loading proposals...',
description: 'Fetching slash proposals you created.',
icon: '🔄',
}}
emptyTableProps={{
title: 'No Proposals Yet',
description: 'You have not proposed any slash proposals yet.',
icon: '🧾',
}}
/>
</TabContent>

<TabContent value={SlashingTab.AGAINST_ME}>
<TangleCloudTable
title="Slashes Against You"
hideTitle
data={againstMe}
isLoading={loadingSlash}
error={slashError}
onRetry={refetchSlashProposals}
tableProps={againstMeTable}
tableConfig={{
tdClassName: '!px-4 !py-3.5 max-w-none whitespace-nowrap',
thClassName: 'whitespace-nowrap',
}}
loadingTableProps={{
title: 'Loading slashes...',
description:
'Fetching slash proposals where you are the targeted operator.',
icon: '🔄',
}}
emptyTableProps={{
title: 'No Slashes Against You',
description:
'There are no slash proposals targeting your operator address.',
icon: '✅',
}}
/>
</TabContent>
</TableAndChartTabs>
</Card>
);
};

export default SlashingTabsTable;
Loading
Loading