Skip to content

Commit 7e8fccf

Browse files
Adding identity balance, vote balance, and vote pubkey to header info
1 parent 91526fd commit 7e8fccf

14 files changed

+231
-33
lines changed

src/api/atoms.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
VoteState,
2222
VoteDistance,
2323
BlockEngineUpdate,
24+
VoteBalance,
2425
} from "./types";
2526
import { DateTime } from "luxon";
2627
import { rafAtom } from "../atomUtils";
@@ -41,6 +42,8 @@ export const tilesAtom = atom<Tile[] | undefined>(undefined);
4142

4243
export const identityBalanceAtom = atom<IdentityBalance | undefined>(undefined);
4344

45+
export const voteBalanceAtom = atom<VoteBalance | undefined>(undefined);
46+
4447
export const rootSlotAtom = atom<RootSlot | undefined>(undefined);
4548

4649
export const optimisticallyConfirmedSlotAtom = atom<

src/api/entities.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,8 @@ export const tileSchema = z.object({
7373

7474
export const identityBalanceSchema = z.number();
7575

76+
export const voteBalanceSchema = z.number();
77+
7678
export const rootSlotSchema = z.number();
7779

7880
export const optimisticallyConfirmedSlotSchema = z.number();
@@ -294,6 +296,10 @@ export const summarySchema = z.discriminatedUnion("key", [
294296
key: z.literal("identity_balance"),
295297
value: identityBalanceSchema,
296298
}),
299+
summaryTopicSchema.extend({
300+
key: z.literal("vote_balance"),
301+
value: identityBalanceSchema,
302+
}),
297303
summaryTopicSchema.extend({
298304
key: z.literal("root_slot"),
299305
value: rootSlotSchema,

src/api/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import {
3737
voteDistanceSchema,
3838
voteStateSchema,
3939
computeUnitsSchema,
40+
voteBalanceSchema,
4041
} from "./entities";
4142

4243
export type Version = z.infer<typeof versionSchema>;
@@ -53,6 +54,8 @@ export type Tile = z.infer<typeof tileSchema>;
5354

5455
export type IdentityBalance = z.infer<typeof identityBalanceSchema>;
5556

57+
export type VoteBalance = z.infer<typeof voteBalanceSchema>;
58+
5659
export type RootSlot = z.infer<typeof rootSlotSchema>;
5760

5861
// export type SlotRooted = z.infer<typeof slotRootedSchema>;

src/api/useSetAtomWsData.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
versionAtom,
1919
voteDistanceAtom,
2020
voteStateAtom,
21+
voteBalanceAtom,
2122
} from "./atoms";
2223
import {
2324
blockEngineSchema,
@@ -69,6 +70,7 @@ export function useSetAtomWsData() {
6970
const setTiles = useSetAtom(tilesAtom);
7071

7172
const setIdentityBalance = useSetAtom(identityBalanceAtom);
73+
const setVoteBalance = useSetAtom(voteBalanceAtom);
7274

7375
const [uptime, setUptime] = useAtom(uptimeAtom);
7476
const uptimeMins =
@@ -174,6 +176,10 @@ export function useSetAtomWsData() {
174176
setIdentityKey(value);
175177
break;
176178
}
179+
case "vote_balance": {
180+
setVoteBalance(value);
181+
break;
182+
}
177183
case "uptime_nanos": {
178184
setUptime({ uptimeNanos: value, ts: DateTime.now() });
179185
break;

src/components/ArrowDropdown.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import { PropsWithChildren, useState } from "react";
22
import { Button } from "@radix-ui/themes";
33
import { CaretDownIcon, CaretUpIcon } from "@radix-ui/react-icons";
4-
import Dropdown from "./Dropdown";
4+
import PopoverDropdown from "./PopoverDropdown";
55

66
export default function ArrowDropdown({ children }: PropsWithChildren) {
77
const [isOpen, setIsOpen] = useState(false);
88

99
return (
10-
<Dropdown dropdownMenu={children} isOpen={isOpen} onOpenChange={setIsOpen}>
10+
<PopoverDropdown
11+
content={children}
12+
isOpen={isOpen}
13+
onOpenChange={setIsOpen}
14+
>
1115
<Button variant="ghost" size="1">
1216
{isOpen ? <CaretUpIcon /> : <CaretDownIcon />}
1317
</Button>
14-
</Dropdown>
18+
</PopoverDropdown>
1519
);
1620
}

src/components/PopoverDropdown.tsx

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { Popover } from "radix-ui";
2+
import styles from "./popoverDropdown.module.css";
3+
import { PropsWithChildren, ReactNode } from "react";
4+
import { containerElAtom } from "../atoms";
5+
import { useAtomValue } from "jotai";
6+
import { Flex } from "@radix-ui/themes";
7+
8+
interface PopoverDropdownProps {
9+
content: ReactNode;
10+
isOpen?: boolean;
11+
onOpenChange?: (isOpen: boolean) => void;
12+
}
13+
14+
export default function PopoverDropdown({
15+
children,
16+
content,
17+
isOpen,
18+
onOpenChange,
19+
}: PropsWithChildren<PopoverDropdownProps>) {
20+
const containerEl = useAtomValue(containerElAtom);
21+
22+
return (
23+
<Popover.Root open={isOpen} onOpenChange={onOpenChange}>
24+
<Flex>
25+
<Popover.Trigger asChild>{children}</Popover.Trigger>
26+
<Popover.Anchor></Popover.Anchor>
27+
</Flex>
28+
<Popover.Portal container={containerEl}>
29+
<Popover.Content
30+
className={styles.popoverContent}
31+
sideOffset={5}
32+
tabIndex={undefined}
33+
>
34+
{content}
35+
</Popover.Content>
36+
</Popover.Portal>
37+
</Popover.Root>
38+
);
39+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
.popover-content {
2+
background: #1c2129;
3+
border-radius: 4px;
4+
box-shadow:
5+
hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
6+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
7+
animation-duration: 400ms;
8+
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
9+
will-change: transform, opacity;
10+
margin-right: 8px;
11+
}
12+
13+
.popover-content:focus {
14+
box-shadow:
15+
hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
16+
hsl(206 22% 7% / 20%) 0px 10px 20px -15px,
17+
0 0 0 2px var(--violet-7);
18+
}
19+
20+
.popover-content[data-state="open"][data-side="top"] {
21+
animation-name: slideDownAndFade;
22+
}
23+
.popover-content[data-state="open"][data-side="right"] {
24+
animation-name: slideLeftAndFade;
25+
}
26+
.popover-content[data-state="open"][data-side="bottom"] {
27+
animation-name: slideUpAndFade;
28+
}
29+
.popover-content[data-state="open"][data-side="left"] {
30+
animation-name: slideRightAndFade;
31+
}
32+
33+
.popover-arrow {
34+
fill: #1c2129;
35+
}
36+
37+
@keyframes slideUpAndFade {
38+
from {
39+
opacity: 0;
40+
transform: translateY(2px);
41+
}
42+
to {
43+
opacity: 1;
44+
transform: translateY(0);
45+
}
46+
}
47+
48+
@keyframes slideRightAndFade {
49+
from {
50+
opacity: 0;
51+
transform: translateX(-2px);
52+
}
53+
to {
54+
opacity: 1;
55+
transform: translateX(0);
56+
}
57+
}
58+
59+
@keyframes slideDownAndFade {
60+
from {
61+
opacity: 0;
62+
transform: translateY(-2px);
63+
}
64+
to {
65+
opacity: 1;
66+
transform: translateY(0);
67+
}
68+
}
69+
70+
@keyframes slideLeftAndFade {
71+
from {
72+
opacity: 0;
73+
transform: translateX(2px);
74+
}
75+
to {
76+
opacity: 1;
77+
transform: translateX(0);
78+
}
79+
}

src/features/Header/IdentityKey.tsx

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
11
import { useAtomValue } from "jotai";
2-
import { identityKeyAtom, uptimeAtom } from "../../api/atoms";
2+
import {
3+
identityBalanceAtom,
4+
uptimeAtom,
5+
voteBalanceAtom,
6+
} from "../../api/atoms";
37
import { Text, Flex, Tooltip } from "@radix-ui/themes";
48
import styles from "./identityKey.module.css";
5-
import usePeer from "../../hooks/usePeer";
69
import PeerIcon from "../../components/PeerIcon";
710
import { myStakePctAtom, myStakeAmountAtom } from "../../atoms";
811
import { PropsWithChildren, useEffect } from "react";
912
import { Duration } from "luxon";
1013
import { getFmtStake, getTimeTillText, slowDateTimeNow } from "../../utils";
1114
import { formatNumber } from "../../numUtils";
1215
import { useInterval, useMedia, useUpdate } from "react-use";
13-
import Dropdown from "../../components/Dropdown";
1416
import clsx from "clsx";
17+
import { useIdentityPeer } from "../../hooks/useIdentityPeer";
18+
import PopoverDropdown from "../../components/PopoverDropdown";
1519

1620
export default function IdentityKey() {
17-
const identityKey = useAtomValue(identityKeyAtom);
18-
const peer = usePeer(identityKey);
21+
const { peer, identityKey } = useIdentityPeer();
1922

20-
const isXXNarrowScreen = useMedia("(min-width: 500px)");
23+
const isXXNarrowScreen = useMedia("(min-width: 550px)");
2124
const isXNarrowScreen = useMedia("(min-width: 750px)");
2225
const isNarrowScreen = useMedia("(min-width: 900px)");
23-
const isWideScreen = useMedia("(min-width: 1366px)");
2426

2527
useEffect(() => {
2628
let title = "Firedancer";
@@ -33,20 +35,17 @@ export default function IdentityKey() {
3335
document.title = title;
3436
}, [identityKey, peer]);
3537

36-
const identityKeyLabel =
37-
isWideScreen || !identityKey
38-
? identityKey
39-
: `${identityKey.substring(0, 8)}...`;
40-
4138
return (
42-
<DropdownContainer showDropdown={!isNarrowScreen}>
43-
<div className={clsx(styles.container, styles.horizontal)}>
39+
<DropdownContainer showDropdown>
40+
<div
41+
className={clsx(styles.container, styles.horizontal, styles.pointer)}
42+
>
4443
{isXXNarrowScreen && (
4544
<PeerIcon url={peer?.info?.icon_url} size={24} isYou />
4645
)}
4746
<Label
4847
label="Validator Name"
49-
value={identityKeyLabel}
48+
value={`${identityKey?.substring(0, 8)}...`}
5049
tooltip="The validators identity public key"
5150
/>
5251
{isXNarrowScreen && (
@@ -59,6 +58,7 @@ export default function IdentityKey() {
5958
<>
6059
<Uptime />
6160
<Commission />
61+
<IdentityBalance />
6262
</>
6363
)}
6464
</div>
@@ -79,15 +79,12 @@ function DropdownContainer({
7979
}
8080

8181
return (
82-
<Dropdown dropdownMenu={<DropdownMenu />} noPadding>
83-
{children}
84-
</Dropdown>
82+
<PopoverDropdown content={<DropdownMenu />}>{children}</PopoverDropdown>
8583
);
8684
}
8785

8886
function DropdownMenu() {
89-
const identityKey = useAtomValue(identityKeyAtom);
90-
const peer = usePeer(identityKey);
87+
const { peer, identityKey } = useIdentityPeer();
9188

9289
return (
9390
<div className={styles.container}>
@@ -103,10 +100,51 @@ function DropdownMenu() {
103100
<StakePct />
104101
<Uptime />
105102
<Commission />
103+
<IdentityBalance />
104+
<VotePubkey />
105+
<VoteBalance />
106106
</div>
107107
);
108108
}
109109

110+
function VotePubkey() {
111+
const { peer } = useIdentityPeer();
112+
113+
return (
114+
<Label
115+
label="Vote Pubkey"
116+
value={peer?.vote[0]?.vote_account}
117+
tooltip="The public key of vote account, encoded in base58"
118+
/>
119+
);
120+
}
121+
122+
function VoteBalance() {
123+
const voteBalance = useAtomValue(voteBalanceAtom);
124+
125+
return (
126+
<>
127+
<Label
128+
label="Vote Balance"
129+
value={getFmtStake(voteBalance) ?? "-"}
130+
tooltip="Account balance of this validators vote account. The balance is on the highest slot of the currently active fork of the validator."
131+
/>
132+
</>
133+
);
134+
}
135+
136+
function IdentityBalance() {
137+
const identityBalance = useAtomValue(identityBalanceAtom);
138+
139+
return (
140+
<Label
141+
label="Identity Balance"
142+
value={getFmtStake(identityBalance) ?? "-"}
143+
tooltip="Account balance of this validators identity account. The balance is on the highest slot of the currently active fork of the validator."
144+
/>
145+
);
146+
}
147+
110148
function StakePct() {
111149
const stakePct = useAtomValue(myStakePctAtom);
112150
let value = "-";
@@ -141,8 +179,7 @@ function StakeValue() {
141179
}
142180

143181
function Commission() {
144-
const identityKey = useAtomValue(identityKeyAtom);
145-
const peer = usePeer(identityKey);
182+
const { peer } = useIdentityPeer();
146183

147184
const maxCommission = peer?.vote.reduce<{
148185
maxStake: number;
@@ -198,15 +235,16 @@ interface LabelProps {
198235
}
199236
function Label({ label, value, color, tooltip }: LabelProps) {
200237
if (!value) return null;
238+
const textValue = (
239+
<Text className={styles.value} style={{ color: color }}>
240+
{value}
241+
</Text>
242+
);
201243

202244
return (
203245
<Flex direction="column">
204246
<Text className={styles.label}>{label}</Text>
205-
<Tooltip content={tooltip}>
206-
<Text className={styles.value} style={{ color: color }}>
207-
{value}
208-
</Text>
209-
</Tooltip>
247+
{tooltip ? <Tooltip content={tooltip}>{textValue}</Tooltip> : textValue}
210248
</Flex>
211249
);
212250
}

0 commit comments

Comments
 (0)