55 renderToHTML ,
66 stringToBlobUrl ,
77 TToken ,
8+ useAllo ,
89 useParams ,
910 useValidateCredential ,
1011} from "common" ;
@@ -35,12 +36,17 @@ import { DefaultLayout } from "../common/DefaultLayout";
3536import { useProject , useProjectApplications } from "./hooks/useProject" ;
3637import NotFoundPage from "../common/NotFoundPage" ;
3738import { useCartStorage } from "../../store" ;
38- import { CartProject } from "../api/types" ;
39+ import { CartProject , ProgressStatus } from "../api/types" ;
3940import InfoModal from "../common/InfoModal" ;
4041import { Input } from "common/src/styles" ;
4142import { PayoutTokenDropdown } from "../round/ViewCartPage/PayoutTokenDropdown" ;
4243import { useAccount } from "wagmi" ;
4344import { 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" ;
4450
4551const CalendarIcon = ( props : React . SVGProps < SVGSVGElement > ) => {
4652 return (
@@ -67,9 +73,9 @@ export default function ViewProject() {
6773 const { chainId } = useAccount ( ) ;
6874 const [ showDirectAllocationModal , setShowDirectAllocationModal ] =
6975 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" ) ;
7379
7480 const payoutTokenOptions : TToken [ ] = getVotingTokenOptions (
7581 Number ( chainId )
@@ -82,6 +88,7 @@ export default function ViewProject() {
8288 const { projectId } = useParams ( ) ;
8389
8490 const dataLayer = useDataLayer ( ) ;
91+ const allo = useAllo ( ) ;
8592
8693 const {
8794 data : projectData ,
@@ -105,6 +112,14 @@ export default function ViewProject() {
105112 dataLayer
106113 ) ;
107114
115+ const {
116+ directAllocation,
117+ tokenApprovalStatus,
118+ fundStatus,
119+ indexingStatus,
120+ txHash,
121+ } = useDirectAllocation ( ) ;
122+
108123 const pastRroundApplications = projectApplications ?. filter (
109124 ( projectApplication ) =>
110125 new Date ( projectApplication . round . donationsEndTime ) < new Date ( )
@@ -127,6 +142,32 @@ export default function ViewProject() {
127142 } ,
128143 ] as BreadcrumbItem [ ] ;
129144
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+
130171 const {
131172 metadata : { title, description = "" , bannerImg } ,
132173 } = project ?? { metadata : { } } ;
@@ -243,47 +284,125 @@ export default function ViewProject() {
243284 < p > Couldn't load project data.</ p >
244285 ) }
245286 </ 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 />
280288 </ div >
281289 </ DefaultLayout >
282290 ) : (
283291 < NotFoundPage />
284292 ) }
285293 </ >
286294 ) ;
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+ }
287406}
288407
289408function ProjectDetailsTabs ( props : {
0 commit comments