@@ -12,6 +12,7 @@ import {
1212} from "@heroicons/react/24/outline" ;
1313import { useWallet } from "@solana/wallet-adapter-react" ;
1414import { useWalletModal } from "@solana/wallet-adapter-react-ui" ;
15+ import type { PublicKey } from "@solana/web3.js" ;
1516import clsx from "clsx" ;
1617import { useSelectedLayoutSegment } from "next/navigation" ;
1718import {
@@ -21,6 +22,8 @@ import {
2122 type ReactNode ,
2223 useCallback ,
2324 useState ,
25+ useMemo ,
26+ type ReactElement ,
2427} from "react" ;
2528import {
2629 Menu ,
@@ -30,6 +33,8 @@ import {
3033 Separator ,
3134 Section ,
3235 SubmenuTrigger ,
36+ Header ,
37+ Collection ,
3338} from "react-aria-components" ;
3439
3540import {
@@ -41,13 +46,18 @@ import {
4146 type States ,
4247 useApi ,
4348} from "../../hooks/use-api" ;
49+ import { StateType as DataStateType , useData } from "../../hooks/use-data" ;
4450import { useLogger } from "../../hooks/use-logger" ;
4551import { usePrimaryDomain } from "../../hooks/use-primary-domain" ;
4652import { AccountHistory } from "../AccountHistory" ;
4753import { Button } from "../Button" ;
4854import { ModalDialog } from "../ModalDialog" ;
4955import { TruncatedKey } from "../TruncatedKey" ;
5056
57+ const ONE_SECOND_IN_MS = 1000 ;
58+ const ONE_MINUTE_IN_MS = 60 * ONE_SECOND_IN_MS ;
59+ const REFRESH_INTERVAL = 1 * ONE_MINUTE_IN_MS ;
60+
5161type Props = Omit < ComponentProps < typeof Button > , "onClick" | "children" > ;
5262
5363export const WalletButton = ( props : Props ) => {
@@ -63,6 +73,15 @@ const WalletButtonImpl = (props: Props) => {
6373 const api = useApi ( ) ;
6474
6575 switch ( api . type ) {
76+ case ApiStateType . WalletDisconnecting :
77+ case ApiStateType . WalletConnecting : {
78+ return (
79+ < ButtonComponent isLoading = { true } { ...props } >
80+ Loading...
81+ </ ButtonComponent >
82+ ) ;
83+ }
84+
6685 case ApiStateType . NotLoaded :
6786 case ApiStateType . NoWallet : {
6887 return < DisconnectedButton { ...props } /> ;
@@ -125,40 +144,15 @@ const ConnectedButton = ({
125144 { api . type === ApiStateType . Loaded && (
126145 < >
127146 < Section className = "flex w-full flex-col" >
128- < SubmenuTrigger >
147+ < StakeAccountSelector api = { api } >
129148 < WalletMenuItem
130149 icon = { BanknotesIcon }
131150 textValue = "Select stake account"
132151 >
133152 < span > Select stake account</ span >
134153 < ChevronRightIcon className = "size-4" />
135154 </ WalletMenuItem >
136- < StyledMenu
137- items = { api . allAccounts . map ( ( account ) => ( {
138- account,
139- id : account . toBase58 ( ) ,
140- } ) ) }
141- >
142- { ( item ) => (
143- < WalletMenuItem
144- onAction = { ( ) => {
145- api . selectAccount ( item . account ) ;
146- } }
147- className = { clsx ( {
148- "font-semibold" : item . account === api . account ,
149- } ) }
150- isDisabled = { item . account === api . account }
151- >
152- < CheckIcon
153- className = { clsx ( "size-4 text-pythpurple-600" , {
154- invisible : item . account !== api . account ,
155- } ) }
156- />
157- < TruncatedKey > { item . account } </ TruncatedKey >
158- </ WalletMenuItem >
159- ) }
160- </ StyledMenu >
161- </ SubmenuTrigger >
155+ </ StakeAccountSelector >
162156 < WalletMenuItem
163157 onAction = { openAccountHistory }
164158 icon = { TableCellsIcon }
@@ -193,14 +187,109 @@ const ConnectedButton = ({
193187 ) ;
194188} ;
195189
190+ type StakeAccountSelectorProps = {
191+ api : States [ ApiStateType . Loaded ] ;
192+ children : ReactElement ;
193+ } ;
194+
195+ const StakeAccountSelector = ( { children, api } : StakeAccountSelectorProps ) => {
196+ const data = useData ( api . dashboardDataCacheKey , api . loadData , {
197+ refreshInterval : REFRESH_INTERVAL ,
198+ } ) ;
199+ const accounts = useMemo ( ( ) => {
200+ if ( data . type === DataStateType . Loaded ) {
201+ const main = api . allAccounts . find ( ( account ) =>
202+ data . data . integrityStakingPublishers . some ( ( publisher ) =>
203+ publisher . stakeAccount ?. equals ( account ) ,
204+ ) ,
205+ ) ;
206+ const other = api . allAccounts
207+ . filter ( ( account ) => account !== main )
208+ . map ( ( account ) => ( {
209+ account,
210+ id : account . toBase58 ( ) ,
211+ } ) ) ;
212+ return { main, other } ;
213+ } else {
214+ return ;
215+ }
216+ } , [ data , api ] ) ;
217+
218+ if ( accounts === undefined ) {
219+ // eslint-disable-next-line unicorn/no-null
220+ return null ;
221+ } else if ( accounts . main === undefined ) {
222+ return accounts . other . length > 1 ? (
223+ < SubmenuTrigger >
224+ { children }
225+ < StyledMenu items = { accounts . other } >
226+ { ( { account } ) => < AccountMenuItem account = { account } api = { api } /> }
227+ </ StyledMenu >
228+ </ SubmenuTrigger >
229+ ) : // eslint-disable-next-line unicorn/no-null
230+ null ;
231+ } else {
232+ return (
233+ < SubmenuTrigger >
234+ { children }
235+ < StyledMenu >
236+ < Section className = "flex w-full flex-col" >
237+ < Header className = "mx-4 text-sm font-semibold" > Main Account</ Header >
238+ < AccountMenuItem account = { accounts . main } api = { api } />
239+ </ Section >
240+ { accounts . other . length > 0 && (
241+ < >
242+ < Separator className = "mx-2 my-1 h-px bg-black/20" />
243+ < Section className = "flex w-full flex-col" >
244+ < Header className = "mx-4 text-sm font-semibold" >
245+ Other Accounts
246+ </ Header >
247+ < Collection items = { accounts . other } >
248+ { ( { account } ) => (
249+ < AccountMenuItem account = { account } api = { api } />
250+ ) }
251+ </ Collection >
252+ </ Section >
253+ </ >
254+ ) }
255+ </ StyledMenu >
256+ </ SubmenuTrigger >
257+ ) ;
258+ }
259+ } ;
260+
261+ type AccountMenuItemProps = {
262+ api : States [ ApiStateType . Loaded ] ;
263+ account : PublicKey ;
264+ } ;
265+
266+ const AccountMenuItem = ( { account, api } : AccountMenuItemProps ) => (
267+ < WalletMenuItem
268+ onAction = { ( ) => {
269+ api . selectAccount ( account ) ;
270+ } }
271+ className = { clsx ( {
272+ "pr-8 font-semibold" : account === api . account ,
273+ } ) }
274+ isDisabled = { account === api . account }
275+ >
276+ < CheckIcon
277+ className = { clsx ( "size-4 text-pythpurple-600" , {
278+ invisible : account !== api . account ,
279+ } ) }
280+ />
281+ < TruncatedKey > { account } </ TruncatedKey >
282+ </ WalletMenuItem >
283+ ) ;
284+
196285const StyledMenu = < T extends object > ( {
197286 className,
198287 ...props
199288} : ComponentProps < typeof Menu < T > > ) => (
200- < Popover className = "data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out focus:outline-none focus-visible:outline-none focus-visible:ring-0" >
289+ < Popover className = "data-[entering]:animate-in data-[exiting]:animate-out data-[entering]:fade-in data-[exiting]:fade-out focus:outline-none focus:ring-0 focus -visible:outline-none focus-visible:ring-0" >
201290 < Menu
202291 className = { clsx (
203- "flex origin-top-right flex-col border border-neutral-400 bg-pythpurple-100 py-2 text-sm text-pythpurple-950 shadow shadow-neutral-400 focus:outline-none focus-visible:outline-none focus-visible:ring-0" ,
292+ "flex origin-top-right flex-col border border-neutral-400 bg-pythpurple-100 py-2 text-sm text-pythpurple-950 shadow shadow-neutral-400 focus:outline-none focus:ring-0 focus -visible:outline-none focus-visible:ring-0" ,
204293 className ,
205294 ) }
206295 { ...props }
0 commit comments