6
6
type ComponentProps ,
7
7
type ReactNode ,
8
8
useCallback ,
9
+ useState ,
9
10
useMemo ,
10
11
} from "react" ;
11
12
import {
@@ -16,8 +17,10 @@ import {
16
17
import background from "./background.png" ;
17
18
import { type States , StateType as ApiStateType } from "../../hooks/use-api" ;
18
19
import { StateType , useAsync } from "../../hooks/use-async" ;
20
+ import { useToast } from "../../hooks/use-toast" ;
19
21
import { Button } from "../Button" ;
20
22
import { Date } from "../Date" ;
23
+ import { ErrorMessage } from "../ErrorMessage" ;
21
24
import { ModalDialog } from "../ModalDialog" ;
22
25
import { Tokens } from "../Tokens" ;
23
26
import { TransferButton } from "../TransferButton" ;
@@ -135,6 +138,7 @@ export const AccountSummary = ({
135
138
max = { walletAmount }
136
139
transfer = { api . deposit }
137
140
submitButtonText = "Add tokens"
141
+ successMessage = "Your tokens have been added to your stake account"
138
142
/>
139
143
) }
140
144
{ availableToWithdraw === 0n ? (
@@ -278,13 +282,20 @@ const OisUnstake = ({
278
282
( ) => staked + warmup + cooldown + cooldown2 ,
279
283
[ staked , warmup , cooldown , cooldown2 ] ,
280
284
) ;
285
+ const toast = useToast ( ) ;
281
286
const { state, execute } = useAsync ( api . unstakeAllIntegrityStaking ) ;
282
287
283
288
const doUnstakeAll = useCallback ( ( ) => {
284
- execute ( ) . catch ( ( ) => {
285
- /* TODO figure out a better UI treatment for when claim fails */
286
- } ) ;
287
- } , [ execute ] ) ;
289
+ execute ( )
290
+ . then ( ( ) => {
291
+ toast . success (
292
+ "Your tokens are now cooling down and will be available to withdraw at the end of the next epoch" ,
293
+ ) ;
294
+ } )
295
+ . catch ( ( error : unknown ) => {
296
+ toast . error ( error ) ;
297
+ } ) ;
298
+ } , [ execute , toast ] ) ;
288
299
289
300
// eslint-disable-next-line unicorn/no-null
290
301
return total === 0n ? null : (
@@ -344,7 +355,7 @@ const OisUnstake = ({
344
355
345
356
type WithdrawButtonProps = Omit <
346
357
ComponentProps < typeof TransferButton > ,
347
- "variant" | "actionDescription" | "actionName" | "transfer"
358
+ "variant" | "actionDescription" | "actionName" | "transfer" | "successMessage"
348
359
> & {
349
360
api : States [ ApiStateType . Loaded ] | States [ ApiStateType . LoadedNoStakeAccount ] ;
350
361
} ;
@@ -354,6 +365,7 @@ const WithdrawButton = ({ api, ...props }: WithdrawButtonProps) => (
354
365
variant = "secondary"
355
366
actionDescription = "Move funds from your account back to your wallet"
356
367
actionName = "Withdraw"
368
+ successMessage = "You have withdrawn tokens from your stake account to your wallet"
357
369
{ ...( api . type === ApiStateType . Loaded && {
358
370
transfer : api . withdraw ,
359
371
} ) }
@@ -419,58 +431,96 @@ const ClaimDialog = ({
419
431
expiringRewards,
420
432
availableRewards,
421
433
} : ClaimDialogProps ) => {
434
+ const [ closeDisabled , setCloseDisabled ] = useState ( false ) ;
435
+
436
+ return (
437
+ < ModalDialog title = "Claim" closeDisabled = { closeDisabled } >
438
+ { ( { close } ) => (
439
+ < ClaimDialogContents
440
+ expiringRewards = { expiringRewards }
441
+ availableRewards = { availableRewards }
442
+ api = { api }
443
+ close = { close }
444
+ setCloseDisabled = { setCloseDisabled }
445
+ />
446
+ ) }
447
+ </ ModalDialog >
448
+ ) ;
449
+ } ;
450
+
451
+ type ClaimDialogContentsProps = {
452
+ availableRewards : bigint ;
453
+ expiringRewards : Date | undefined ;
454
+ api : States [ ApiStateType . Loaded ] ;
455
+ close : ( ) => void ;
456
+ setCloseDisabled : ( value : boolean ) => void ;
457
+ } ;
458
+
459
+ const ClaimDialogContents = ( {
460
+ api,
461
+ expiringRewards,
462
+ availableRewards,
463
+ close,
464
+ setCloseDisabled,
465
+ } : ClaimDialogContentsProps ) => {
422
466
const { state, execute } = useAsync ( api . claim ) ;
423
467
468
+ const toast = useToast ( ) ;
469
+
424
470
const doClaim = useCallback ( ( ) => {
425
- execute ( ) . catch ( ( ) => {
426
- /* TODO figure out a better UI treatment for when claim fails */
427
- } ) ;
428
- } , [ execute ] ) ;
471
+ setCloseDisabled ( true ) ;
472
+ execute ( )
473
+ . then ( ( ) => {
474
+ close ( ) ;
475
+ toast . success ( "You have claimed your rewards" ) ;
476
+ } )
477
+ . catch ( ( ) => {
478
+ /* no-op since this is already handled in the UI using `state` and is logged in useAsync */
479
+ } )
480
+ . finally ( ( ) => {
481
+ setCloseDisabled ( false ) ;
482
+ } ) ;
483
+ } , [ execute , toast ] ) ;
429
484
430
485
return (
431
- < ModalDialog title = "Claim" >
432
- { ( { close } ) => (
433
- < >
434
- < p className = "mb-4" >
435
- Claim your < Tokens > { availableRewards } </ Tokens > rewards
436
- </ p >
437
- { expiringRewards && (
438
- < div className = "mb-4 flex max-w-96 flex-row gap-2 border border-neutral-600/50 bg-pythpurple-400/20 p-4" >
439
- < InformationCircleIcon className = "size-8 flex-none" />
440
- < div className = "text-sm" >
441
- Rewards expire one year from the epoch in which they were
442
- earned. You have rewards expiring on{ " " }
443
- < Date > { expiringRewards } </ Date > .
444
- </ div >
445
- </ div >
446
- ) }
447
- { state . type === StateType . Error && (
448
- < p className = "mt-8 text-red-600" >
449
- Uh oh, an error occurred! Please try again
450
- </ p >
451
- ) }
452
- < div className = "mt-14 flex flex-col gap-8 sm:flex-row sm:justify-between" >
453
- < Button
454
- variant = "secondary"
455
- className = "w-full sm:w-auto"
456
- size = "noshrink"
457
- onPress = { close }
458
- >
459
- Cancel
460
- </ Button >
461
- < Button
462
- className = "w-full sm:w-auto"
463
- size = "noshrink"
464
- isDisabled = { state . type === StateType . Complete }
465
- isLoading = { state . type === StateType . Running }
466
- onPress = { doClaim }
467
- >
468
- Claim
469
- </ Button >
486
+ < >
487
+ < p className = "mb-4" >
488
+ Claim your < Tokens > { availableRewards } </ Tokens > rewards
489
+ </ p >
490
+ { expiringRewards && (
491
+ < div className = "mb-4 flex max-w-96 flex-row gap-2 border border-neutral-600/50 bg-pythpurple-400/20 p-4" >
492
+ < InformationCircleIcon className = "size-8 flex-none" />
493
+ < div className = "text-sm" >
494
+ Rewards expire one year from the epoch in which they were earned.
495
+ You have rewards expiring on < Date > { expiringRewards } </ Date > .
470
496
</ div >
471
- </ >
497
+ </ div >
472
498
) }
473
- </ ModalDialog >
499
+ { state . type === StateType . Error && (
500
+ < div className = "mt-4 max-w-sm" >
501
+ < ErrorMessage error = { state . error } />
502
+ </ div >
503
+ ) }
504
+ < div className = "mt-14 flex flex-col gap-8 sm:flex-row sm:justify-between" >
505
+ < Button
506
+ variant = "secondary"
507
+ className = "w-full sm:w-auto"
508
+ size = "noshrink"
509
+ onPress = { close }
510
+ >
511
+ Cancel
512
+ </ Button >
513
+ < Button
514
+ className = "w-full sm:w-auto"
515
+ size = "noshrink"
516
+ isDisabled = { state . type === StateType . Complete }
517
+ isLoading = { state . type === StateType . Running }
518
+ onPress = { doClaim }
519
+ >
520
+ Claim
521
+ </ Button >
522
+ </ div >
523
+ </ >
474
524
) ;
475
525
} ;
476
526
@@ -484,11 +534,17 @@ type ClaimButtonProps = Omit<
484
534
const ClaimButton = ( { api, ...props } : ClaimButtonProps ) => {
485
535
const { state, execute } = useAsync ( api . claim ) ;
486
536
537
+ const toast = useToast ( ) ;
538
+
487
539
const doClaim = useCallback ( ( ) => {
488
- execute ( ) . catch ( ( ) => {
489
- /* TODO figure out a better UI treatment for when claim fails */
490
- } ) ;
491
- } , [ execute ] ) ;
540
+ execute ( )
541
+ . then ( ( ) => {
542
+ toast . success ( "You have claimed your rewards" ) ;
543
+ } )
544
+ . catch ( ( error : unknown ) => {
545
+ toast . error ( error ) ;
546
+ } ) ;
547
+ } , [ execute , toast ] ) ;
492
548
493
549
return (
494
550
< Button
0 commit comments