5
5
renderToHTML ,
6
6
stringToBlobUrl ,
7
7
TToken ,
8
+ useAllo ,
8
9
useParams ,
9
10
useValidateCredential ,
10
11
} from "common" ;
@@ -35,12 +36,17 @@ import { DefaultLayout } from "../common/DefaultLayout";
35
36
import { useProject , useProjectApplications } from "./hooks/useProject" ;
36
37
import NotFoundPage from "../common/NotFoundPage" ;
37
38
import { useCartStorage } from "../../store" ;
38
- import { CartProject } from "../api/types" ;
39
+ import { CartProject , ProgressStatus } from "../api/types" ;
39
40
import InfoModal from "../common/InfoModal" ;
40
41
import { Input } from "common/src/styles" ;
41
42
import { PayoutTokenDropdown } from "../round/ViewCartPage/PayoutTokenDropdown" ;
42
43
import { useAccount } from "wagmi" ;
43
44
import { getVotingTokenOptions } from "../api/utils" ;
45
+ import ErrorModal from "../common/ErrorModal" ;
46
+ import ProgressModal , { errorModalDelayMs } from "../common/ProgressModal" ;
47
+ import { useDirectAllocation } from "./hooks/useDirectAllocation" ;
48
+ import { getDirectAllocationPoolId } from "common/dist/allo/backends/allo-v2" ;
49
+ import { zeroAddress } from "viem" ;
44
50
45
51
const CalendarIcon = ( props : React . SVGProps < SVGSVGElement > ) => {
46
52
return (
@@ -67,9 +73,9 @@ export default function ViewProject() {
67
73
const { chainId } = useAccount ( ) ;
68
74
const [ showDirectAllocationModal , setShowDirectAllocationModal ] =
69
75
useState < boolean > ( false ) ;
70
- const [ fixedDonation , setFixedDonation ] = useState < string | undefined > (
71
- undefined
72
- ) ;
76
+ const [ openProgressModal , setOpenProgressModal ] = useState ( false ) ;
77
+ const [ directDonationAmount , setDirectDonationAmount ] =
78
+ useState < string > ( "0.0" ) ;
73
79
74
80
const payoutTokenOptions : TToken [ ] = getVotingTokenOptions (
75
81
Number ( chainId )
@@ -82,6 +88,7 @@ export default function ViewProject() {
82
88
const { projectId } = useParams ( ) ;
83
89
84
90
const dataLayer = useDataLayer ( ) ;
91
+ const allo = useAllo ( ) ;
85
92
86
93
const {
87
94
data : projectData ,
@@ -105,6 +112,14 @@ export default function ViewProject() {
105
112
dataLayer
106
113
) ;
107
114
115
+ const {
116
+ directAllocation,
117
+ tokenApprovalStatus,
118
+ fundStatus,
119
+ indexingStatus,
120
+ txHash,
121
+ } = useDirectAllocation ( ) ;
122
+
108
123
const pastRroundApplications = projectApplications ?. filter (
109
124
( projectApplication ) =>
110
125
new Date ( projectApplication . round . donationsEndTime ) < new Date ( )
@@ -127,6 +142,32 @@ export default function ViewProject() {
127
142
} ,
128
143
] as BreadcrumbItem [ ] ;
129
144
145
+ const progressSteps = [
146
+ {
147
+ name : "Token Approval" ,
148
+ description : "Approving token transfer." ,
149
+ status : tokenApprovalStatus ,
150
+ } ,
151
+ {
152
+ name : "Donating" ,
153
+ description : "Donating to the project." ,
154
+ status : fundStatus ,
155
+ } ,
156
+ {
157
+ name : "Indexing" ,
158
+ description : "Indexing the data." ,
159
+ status : indexingStatus ,
160
+ } ,
161
+ {
162
+ name : "Redirecting" ,
163
+ description : "Just another moment while we finish things up." ,
164
+ status :
165
+ indexingStatus === ProgressStatus . IS_SUCCESS
166
+ ? ProgressStatus . IN_PROGRESS
167
+ : ProgressStatus . NOT_STARTED ,
168
+ } ,
169
+ ] ;
170
+
130
171
const {
131
172
metadata : { title, description = "" , bannerImg } ,
132
173
} = project ?? { metadata : { } } ;
@@ -243,47 +284,125 @@ export default function ViewProject() {
243
284
< p > Couldn't load project data.</ p >
244
285
) }
245
286
</ div >
246
- < InfoModal
247
- title = "Donate!"
248
- body = {
249
- < div className = "flex gap-4" >
250
- < p className = "mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium" >
251
- Amount
252
- </ p >
253
- < Input
254
- aria-label = { "Donation amount for all projects " }
255
- id = { "input-donationamount" }
256
- min = "0"
257
- type = "number"
258
- value = { fixedDonation ?? "" }
259
- onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) => {
260
- setFixedDonation ( e . target . value ) ;
261
- } }
262
- className = "w-16 lg:w-18"
263
- />
264
- < PayoutTokenDropdown
265
- selectedPayoutToken = { payoutToken }
266
- setSelectedPayoutToken = { ( token ) => {
267
- setPayoutToken ( token ) ;
268
- } }
269
- payoutTokenOptions = { payoutTokenOptions }
270
- />
271
- </ div >
272
- }
273
- isOpen = { showDirectAllocationModal }
274
- cancelButtonAction = { ( ) => setShowDirectAllocationModal ( false ) }
275
- continueButtonAction = { ( ) => setShowDirectAllocationModal ( false ) }
276
- // disableContinueButton={true}
277
- continueButtonText = { "Donate" }
278
- setIsOpen = { setShowDirectAllocationModal }
279
- />
287
+ < DirectDonationModals />
280
288
</ div >
281
289
</ DefaultLayout >
282
290
) : (
283
291
< NotFoundPage />
284
292
) }
285
293
</ >
286
294
) ;
295
+
296
+ function DirectDonationModals ( ) {
297
+ return (
298
+ < >
299
+ < InfoModal
300
+ title = "Donate!"
301
+ body = {
302
+ < div className = "flex gap-4" >
303
+ < p className = "mt-4 md:mt-3 text-xs md:text-sm amount-text font-medium" >
304
+ Amount
305
+ </ p >
306
+ < Input
307
+ aria-label = { "Donation amount for all projects " }
308
+ id = { "input-donationamount" }
309
+ min = "0"
310
+ type = "number"
311
+ value = { directDonationAmount }
312
+ onChange = { ( e : React . ChangeEvent < HTMLInputElement > ) => {
313
+ setDirectDonationAmount ( e . target . value ) ;
314
+ } }
315
+ className = "w-16 lg:w-18"
316
+ />
317
+ < PayoutTokenDropdown
318
+ selectedPayoutToken = { payoutToken }
319
+ setSelectedPayoutToken = { ( token ) => {
320
+ setPayoutToken ( token ) ;
321
+ } }
322
+ payoutTokenOptions = { payoutTokenOptions }
323
+ />
324
+ </ div >
325
+ }
326
+ isOpen = { showDirectAllocationModal }
327
+ cancelButtonAction = { ( ) => setShowDirectAllocationModal ( false ) }
328
+ continueButtonAction = { ( ) => {
329
+ setShowDirectAllocationModal ( false ) ;
330
+ setOpenProgressModal ( true ) ;
331
+ handleDonate ( ) ;
332
+ } }
333
+ // disableContinueButton={true}
334
+ continueButtonText = { "Donate" }
335
+ setIsOpen = { setShowDirectAllocationModal }
336
+ />
337
+ < ProgressModal
338
+ isOpen = { openProgressModal }
339
+ subheading = { "Please hold while we donate your funds to the project." }
340
+ steps = { progressSteps }
341
+ />
342
+ { /* <ErrorModal
343
+ isOpen={openErrorModal}
344
+ setIsOpen={setOpenErrorModal}
345
+ tryAgainFn={handleSubmitFund}
346
+ subheading={errorModalSubHeading}
347
+ /> */ }
348
+ </ >
349
+ ) ;
350
+ }
351
+
352
+ async function handleDonate ( ) {
353
+ if (
354
+ directDonationAmount === undefined ||
355
+ allo === null ||
356
+ payoutToken === undefined
357
+ ) {
358
+ return ;
359
+ }
360
+ try {
361
+ setTimeout ( ( ) => {
362
+ setOpenProgressModal ( true ) ;
363
+ } , errorModalDelayMs ) ;
364
+
365
+ let requireTokenApproval = false ;
366
+
367
+ const poolId = getDirectAllocationPoolId ( chainId ?? 1 ) ?. toString ( ) ;
368
+
369
+ const recipient = project ?. roles ?. filter (
370
+ ( role ) => role . role === "OWNER"
371
+ ) [ 0 ] . address ;
372
+
373
+ const nonce = project ?. nonce ;
374
+
375
+ if (
376
+ poolId === undefined ||
377
+ recipient === undefined ||
378
+ nonce === undefined
379
+ ) {
380
+ console . error ( "handleDonation - project" , projectId , "missing data" ) ;
381
+ return ;
382
+ }
383
+
384
+ if (
385
+ payoutToken ?. address !== undefined &&
386
+ payoutToken ?. address !== zeroAddress
387
+ ) {
388
+ requireTokenApproval = true ;
389
+ }
390
+
391
+ await directAllocation ( {
392
+ allo,
393
+ poolId,
394
+ fundAmount : Number (
395
+ parseFloat ( directDonationAmount ) . toFixed ( payoutToken . decimals )
396
+ ) ,
397
+ payoutToken,
398
+ recipient,
399
+ nonce,
400
+ requireTokenApproval,
401
+ } ) ;
402
+ } catch ( error ) {
403
+ console . error ( "handleDonation - project" , projectId , error ) ;
404
+ }
405
+ }
287
406
}
288
407
289
408
function ProjectDetailsTabs ( props : {
0 commit comments