@@ -12,6 +12,7 @@ import {
12
12
} from "@heroicons/react/24/outline" ;
13
13
import { useWallet } from "@solana/wallet-adapter-react" ;
14
14
import { useWalletModal } from "@solana/wallet-adapter-react-ui" ;
15
+ import type { PublicKey } from "@solana/web3.js" ;
15
16
import clsx from "clsx" ;
16
17
import { useSelectedLayoutSegment } from "next/navigation" ;
17
18
import {
@@ -21,6 +22,8 @@ import {
21
22
type ReactNode ,
22
23
useCallback ,
23
24
useState ,
25
+ useMemo ,
26
+ type ReactElement ,
24
27
} from "react" ;
25
28
import {
26
29
Menu ,
@@ -30,6 +33,8 @@ import {
30
33
Separator ,
31
34
Section ,
32
35
SubmenuTrigger ,
36
+ Header ,
37
+ Collection ,
33
38
} from "react-aria-components" ;
34
39
35
40
import {
@@ -41,13 +46,18 @@ import {
41
46
type States ,
42
47
useApi ,
43
48
} from "../../hooks/use-api" ;
49
+ import { StateType as DataStateType , useData } from "../../hooks/use-data" ;
44
50
import { useLogger } from "../../hooks/use-logger" ;
45
51
import { usePrimaryDomain } from "../../hooks/use-primary-domain" ;
46
52
import { AccountHistory } from "../AccountHistory" ;
47
53
import { Button } from "../Button" ;
48
54
import { ModalDialog } from "../ModalDialog" ;
49
55
import { TruncatedKey } from "../TruncatedKey" ;
50
56
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
+
51
61
type Props = Omit < ComponentProps < typeof Button > , "onClick" | "children" > ;
52
62
53
63
export const WalletButton = ( props : Props ) => {
@@ -63,6 +73,15 @@ const WalletButtonImpl = (props: Props) => {
63
73
const api = useApi ( ) ;
64
74
65
75
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
+
66
85
case ApiStateType . NotLoaded :
67
86
case ApiStateType . NoWallet : {
68
87
return < DisconnectedButton { ...props } /> ;
@@ -125,40 +144,15 @@ const ConnectedButton = ({
125
144
{ api . type === ApiStateType . Loaded && (
126
145
< >
127
146
< Section className = "flex w-full flex-col" >
128
- < SubmenuTrigger >
147
+ < StakeAccountSelector api = { api } >
129
148
< WalletMenuItem
130
149
icon = { BanknotesIcon }
131
150
textValue = "Select stake account"
132
151
>
133
152
< span > Select stake account</ span >
134
153
< ChevronRightIcon className = "size-4" />
135
154
</ 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 >
162
156
< WalletMenuItem
163
157
onAction = { openAccountHistory }
164
158
icon = { TableCellsIcon }
@@ -193,14 +187,109 @@ const ConnectedButton = ({
193
187
) ;
194
188
} ;
195
189
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
+
196
285
const StyledMenu = < T extends object > ( {
197
286
className,
198
287
...props
199
288
} : 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" >
201
290
< Menu
202
291
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" ,
204
293
className ,
205
294
) }
206
295
{ ...props }
0 commit comments