@@ -47,7 +47,7 @@ import ErrorModal from "../common/ErrorModal";
47
47
import ProgressModal , { errorModalDelayMs } from "../common/ProgressModal" ;
48
48
import { useDirectAllocation } from "./hooks/useDirectAllocation" ;
49
49
import { getDirectAllocationPoolId } from "common/dist/allo/backends/allo-v2" ;
50
- import { zeroAddress } from "viem" ;
50
+ import { getAddress , zeroAddress } from "viem" ;
51
51
import GenericModal from "../common/GenericModal" ;
52
52
import { BoltIcon , InformationCircleIcon } from "@heroicons/react/24/outline" ;
53
53
import { useConnectModal } from "@rainbow-me/rainbowkit" ;
@@ -89,53 +89,16 @@ export default function ViewProject() {
89
89
const [ errorModalSubHeading , setErrorModalSubHeading ] = useState <
90
90
string | undefined
91
91
> ( ) ;
92
- const [ directDonationAmount , setDirectDonationAmount ] = useState < string > ( "" ) ;
93
92
93
+ const [ directDonationAmount , setDirectDonationAmount ] = useState < string > ( "" ) ;
94
94
const payoutTokenOptions : TToken [ ] = getVotingTokenOptions (
95
95
Number ( chainId )
96
96
) . filter ( ( p ) => p . canVote ) ;
97
-
98
97
const [ payoutToken , setPayoutToken ] = useState < TToken | undefined > (
99
98
payoutTokenOptions [ 0 ]
100
99
) ;
101
-
102
- const [ tokenBalance , setTokenBalance ] = useState < bigint > ( BigInt ( "0" ) ) ;
103
100
const directAllocationPoolId = getDirectAllocationPoolId ( chainId ?? 1 ) ;
104
-
105
- useEffect ( ( ) => {
106
- const runner = async ( ) => {
107
- const { value } = await getBalance ( config , {
108
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
109
- address : address ! ,
110
- token :
111
- payoutToken ?. address === zeroAddress ||
112
- payoutToken ?. address . toLowerCase ( ) === NATIVE . toLowerCase ( )
113
- ? undefined
114
- : payoutToken ?. address ,
115
- chainId,
116
- } ) ;
117
-
118
- setTokenBalance ( value ) ;
119
- } ;
120
- if ( address && address !== zeroAddress ) runner ( ) ;
121
- } , [ payoutToken , chainId , address ] ) ;
122
-
123
- const hasEnoughFunds =
124
- Number ( directDonationAmount ) <=
125
- Number ( ethers . utils . formatUnits ( tokenBalance , payoutToken ?. decimals ?? 18 ) ) ;
126
-
127
- const [ hasClickedSubmit , setHasClickedSubmit ] = useState ( false ) ;
128
- const [ isEmptyInput , setIsEmptyInput ] = useState ( false ) ;
129
101
const [ transactionReplaced , setTransactionReplaced ] = useState ( false ) ;
130
-
131
- useEffect ( ( ) => {
132
- if ( directDonationAmount === "" || Number ( directDonationAmount ) === 0 ) {
133
- setIsEmptyInput ( true ) ;
134
- } else {
135
- setIsEmptyInput ( false ) ;
136
- }
137
- } , [ directDonationAmount ] ) ;
138
-
139
102
const { projectId } = useParams ( ) ;
140
103
141
104
const dataLayer = useDataLayer ( ) ;
@@ -394,7 +357,7 @@ export default function ViewProject() {
394
357
< p > Couldn't load project data.</ p >
395
358
) }
396
359
</ div >
397
- < DirectDonationModals />
360
+ < DonationModals />
398
361
</ div >
399
362
</ DefaultLayout >
400
363
) : (
@@ -403,98 +366,26 @@ export default function ViewProject() {
403
366
</ >
404
367
) ;
405
368
406
- function DirectDonationModals ( ) {
369
+ function DonationModals ( ) {
407
370
return (
408
371
< >
409
372
< GenericModal
410
373
body = {
411
- < >
412
- < div >
413
- < p className = "mb-4" >
414
- < BoltIcon className = "w-4 h-4 mb-1 inline-block mr-2" />
415
- Donate now
416
- </ p >
417
- </ div >
418
-
419
- < div className = "mb-4 flex flex-col lg:flex-row justify-between sm:px-2 px-2 py-4 rounded-md" >
420
- < div className = "flex" >
421
- < div className = "flex relative overflow-hidden bg-no-repeat bg-cover mt-auto mb-auto" >
422
- < img
423
- className = "inline-block rounded-full w-10 my-auto mr-2"
424
- src = {
425
- projectData ?. project . metadata . logoImg
426
- ? `${ ipfsGateway } /ipfs/${ projectData ?. project . metadata . logoImg } `
427
- : DefaultLogoImage
428
- }
429
- alt = { "Project Logo" }
430
- />
431
- < p className = "font-semibold text-md my-auto text-ellipsis line-clamp-1 max-w-[500px] 2xl:max-w-none" >
432
- { projectData ?. project ?. metadata . title }
433
- </ p >
434
- </ div >
435
- </ div >
436
- < div className = "flex sm:space-x-4 space-x-2 h-16 sm:pl-4 pt-3 justify-center" >
437
- < p className = "mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium" >
438
- Amount
439
- </ p >
440
- < Input
441
- aria-label = { "Donation amount for all projects " }
442
- id = { "input-donationamount" }
443
- min = "0"
444
- type = "text"
445
- value = { directDonationAmount }
446
- onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) => {
447
- const value = e . target . value . replace ( "," , "." ) ;
448
- if ( / ^ \d * \. ? \d * $ / . test ( value ) || value === "" ) {
449
- setDirectDonationAmount ( value ) ;
450
- }
451
- } }
452
- className = "w-16 lg:w-18"
453
- />
454
- < PayoutTokenDropdown
455
- selectedPayoutToken = { payoutToken }
456
- setSelectedPayoutToken = { ( token ) => {
457
- setPayoutToken ( token ) ;
458
- } }
459
- payoutTokenOptions = { payoutTokenOptions }
460
- style = "max-h-16"
461
- />
462
- </ div >
463
- </ div >
464
- { isEmptyInput && hasClickedSubmit && (
465
- < p
466
- data-testid = "emptyInput"
467
- className = "rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
468
- >
469
- < InformationCircleIcon className = "w-4 h-4 mr-1 mt-0.5" />
470
- < span > You must enter donation for the project</ span >
471
- </ p >
472
- ) }
473
- { ! hasEnoughFunds && (
474
- < p
475
- data-testid = "hasEnoughFunds"
476
- className = "rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
477
- >
478
- < InformationCircleIcon className = "w-4 h-4 mr-1 mt-0.5" />
479
- < span > You don't have enough funds</ span >
480
- </ p >
481
- ) }
482
-
483
- < button
484
- type = "button"
485
- className = "w-full font-normal rounded-lg bg-gitcoin-violet-400 text-white focus-visible:outline-indigo-600 py-2 leading-6"
486
- onClick = { ( ) => {
487
- handleDonate ( ) ;
488
- } }
489
- disabled = { ! hasEnoughFunds }
490
- >
491
- Submit your donation
492
- </ button >
493
- </ >
374
+ < DirectDonationModalComponent
375
+ chainId = { chainId ! }
376
+ address = { address ! }
377
+ directDonationAmount = { directDonationAmount }
378
+ setDirectDonationAmount = { setDirectDonationAmount }
379
+ payoutToken = { payoutToken }
380
+ setPayoutToken = { setPayoutToken }
381
+ payoutTokenOptions = { payoutTokenOptions }
382
+ projectData = { { project : project ! } }
383
+ handleDonate = { handleDonate }
384
+ />
494
385
}
495
386
isOpen = { showDirectAllocationModal }
496
387
setIsOpen = { setShowDirectAllocationModal }
497
- / >
388
+ > </ GenericModal >
498
389
< ProgressModal
499
390
isOpen = { openProgressModal }
500
391
subheading = { "Please hold while we donate your funds to the project." }
@@ -514,10 +405,8 @@ export default function ViewProject() {
514
405
if (
515
406
directDonationAmount === undefined ||
516
407
allo === null ||
517
- payoutToken === undefined ||
518
- isEmptyInput
408
+ payoutToken === undefined
519
409
) {
520
- setHasClickedSubmit ( true ) ;
521
410
return ;
522
411
}
523
412
@@ -572,6 +461,154 @@ export default function ViewProject() {
572
461
}
573
462
}
574
463
464
+ export function DirectDonationModalComponent ( props : {
465
+ chainId : number ;
466
+ address : string ;
467
+ directDonationAmount : string ;
468
+ setDirectDonationAmount : ( value : string ) => void ;
469
+ payoutToken : TToken | undefined ;
470
+ setPayoutToken : ( token : TToken | undefined ) => void ;
471
+ payoutTokenOptions : TToken [ ] ;
472
+ projectData : { project : v2Project } ;
473
+ handleDonate : ( ) => void ;
474
+ } ) {
475
+ const [ tokenBalance , setTokenBalance ] = useState < bigint > ( BigInt ( "0" ) ) ;
476
+ const [ hasEnoughFunds , setHasEnoughFunds ] = useState ( false ) ;
477
+
478
+ useEffect ( ( ) => {
479
+ const runner = async ( ) => {
480
+ const { value } = await getBalance ( config , {
481
+ address : getAddress ( props . address ) ,
482
+ token :
483
+ props . payoutToken ?. address === zeroAddress ||
484
+ props . payoutToken ?. address . toLowerCase ( ) === NATIVE . toLowerCase ( )
485
+ ? undefined
486
+ : props . payoutToken ?. address ,
487
+ chainId : props . chainId ,
488
+ } ) ;
489
+
490
+ setTokenBalance ( value ) ;
491
+ } ;
492
+ if ( props . address && props . address !== zeroAddress ) runner ( ) ;
493
+ } , [ props . payoutToken , props . chainId , props . address ] ) ;
494
+
495
+ useMemo ( ( ) => {
496
+ setHasEnoughFunds (
497
+ Number ( props . directDonationAmount ) <=
498
+ Number (
499
+ ethers . utils . formatUnits (
500
+ tokenBalance ,
501
+ props . payoutToken ?. decimals ?? 18
502
+ )
503
+ )
504
+ ) ;
505
+ } , [ props . directDonationAmount , tokenBalance , props . payoutToken ] ) ;
506
+
507
+ const [ hasClickedSubmit , setHasClickedSubmit ] = useState ( false ) ;
508
+ const [ isEmptyInput , setIsEmptyInput ] = useState ( false ) ;
509
+
510
+ useEffect ( ( ) => {
511
+ if (
512
+ props . directDonationAmount === "" ||
513
+ Number ( props . directDonationAmount ) === 0
514
+ ) {
515
+ setIsEmptyInput ( true ) ;
516
+ } else {
517
+ setIsEmptyInput ( false ) ;
518
+ }
519
+ } , [ props . directDonationAmount ] ) ;
520
+
521
+ return (
522
+ < >
523
+ < div >
524
+ < p className = "mb-4" >
525
+ < BoltIcon className = "w-4 h-4 mb-1 inline-block mr-2" />
526
+ Donate now
527
+ </ p >
528
+ </ div >
529
+
530
+ < div className = "mb-4 flex flex-col lg:flex-row justify-between sm:px-2 px-2 py-4 rounded-md" >
531
+ < div className = "flex" >
532
+ < div className = "flex relative overflow-hidden bg-no-repeat bg-cover mt-auto mb-auto" >
533
+ < img
534
+ className = "inline-block rounded-full w-10 my-auto mr-2"
535
+ src = {
536
+ props . projectData ?. project . metadata . logoImg
537
+ ? `${ ipfsGateway } /ipfs/${ props . projectData ?. project . metadata . logoImg } `
538
+ : DefaultLogoImage
539
+ }
540
+ alt = { "Project Logo" }
541
+ />
542
+ < p className = "font-semibold text-md my-auto text-ellipsis line-clamp-1 max-w-[500px] 2xl:max-w-none" >
543
+ { props . projectData ?. project ?. metadata . title }
544
+ </ p >
545
+ </ div >
546
+ </ div >
547
+ < div className = "flex sm:space-x-4 space-x-2 h-16 sm:pl-4 pt-3 justify-center" >
548
+ < p className = "mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium" >
549
+ Amount
550
+ </ p >
551
+ < Input
552
+ aria-label = { "Donation amount for all projects " }
553
+ id = { "input-donationamount" }
554
+ min = "0"
555
+ type = "text"
556
+ value = { props . directDonationAmount }
557
+ onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) => {
558
+ const value = e . target . value . replace ( "," , "." ) ;
559
+ if ( / ^ \d * \. ? \d * $ / . test ( value ) || value === "" ) {
560
+ props . setDirectDonationAmount ( value ) ;
561
+ }
562
+ } }
563
+ className = "w-16 lg:w-18"
564
+ />
565
+ < PayoutTokenDropdown
566
+ selectedPayoutToken = { props . payoutToken }
567
+ setSelectedPayoutToken = { ( token ) => {
568
+ props . setPayoutToken ( token ) ;
569
+ } }
570
+ payoutTokenOptions = { props . payoutTokenOptions }
571
+ style = "max-h-16"
572
+ />
573
+ </ div >
574
+ </ div >
575
+ { isEmptyInput && hasClickedSubmit && (
576
+ < p
577
+ data-testid = "emptyInput"
578
+ className = "rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
579
+ >
580
+ < InformationCircleIcon className = "w-4 h-4 mr-1 mt-0.5" />
581
+ < span > You must enter donation for the project</ span >
582
+ </ p >
583
+ ) }
584
+ { ! hasEnoughFunds && (
585
+ < p
586
+ data-testid = "hasEnoughFunds"
587
+ className = "rounded-md bg-red-50 py-2 text-pink-500 flex justify-center my-4 text-sm"
588
+ >
589
+ < InformationCircleIcon className = "w-4 h-4 mr-1 mt-0.5" />
590
+ < span > You don't have enough funds</ span >
591
+ </ p >
592
+ ) }
593
+
594
+ < button
595
+ type = "button"
596
+ className = "w-full font-normal rounded-lg bg-gitcoin-violet-400 text-white focus-visible:outline-indigo-600 py-2 leading-6"
597
+ onClick = { ( ) => {
598
+ if ( isEmptyInput ) {
599
+ setHasClickedSubmit ( true ) ;
600
+ return ;
601
+ }
602
+ props . handleDonate ( ) ;
603
+ } }
604
+ disabled = { ! hasEnoughFunds }
605
+ >
606
+ Submit your donation
607
+ </ button >
608
+ </ >
609
+ ) ;
610
+ }
611
+
575
612
function ProjectDetailsTabs ( props : {
576
613
tabs : string [ ] ;
577
614
onChange ?: ( tabIndex : number ) => void ;
0 commit comments