@@ -9,9 +9,18 @@ import Team, { TeamFormData } from './Team';
99import Contact , { ContactFormData } from './Contact' ;
1010import LoadingScreen from './LoadingScreen' ;
1111import SuccessScreen from './SuccessScreen' ;
12+ import TransactionSigningScreen from './TransactionSigningScreen' ;
1213import { z } from 'zod' ;
13- import { createCrowdfundingProject } from '@/lib/api/project' ;
14- import { CreateCrowdfundingProjectRequest } from '@/lib/api/types' ;
14+ import {
15+ prepareCrowdfundingProject ,
16+ confirmCrowdfundingProject ,
17+ } from '@/lib/api/project' ;
18+ import {
19+ CreateCrowdfundingProjectRequest ,
20+ PrepareCrowdfundingProjectResponse ,
21+ } from '@/lib/api/types' ;
22+ import { useWalletSigning } from '@/hooks/use-wallet' ;
23+ import { useWalletProtection } from '@/hooks/use-wallet-protection' ;
1524
1625type StepHandle = { validate : ( ) => boolean ; markSubmitted ?: ( ) => void } ;
1726
@@ -34,6 +43,18 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
3443 const [ submitErrors , setSubmitErrors ] = useState < string [ ] > ( [ ] ) ;
3544 const [ showSuccess , setShowSuccess ] = useState ( false ) ;
3645 const [ isLoading , setIsLoading ] = useState ( false ) ;
46+ const [ unsignedTransaction , setUnsignedTransaction ] = useState < string | null > (
47+ null
48+ ) ;
49+ const [ isSigningTransaction , setIsSigningTransaction ] = useState ( false ) ;
50+
51+ // New state for two-step flow
52+ const [ preparedData , setPreparedData ] = useState <
53+ PrepareCrowdfundingProjectResponse [ 'data' ] | null
54+ > ( null ) ;
55+ const [ flowStep , setFlowStep ] = useState <
56+ 'form' | 'preparing' | 'signing' | 'confirming' | 'success'
57+ > ( 'form' ) ;
3758
3859 // Form data state
3960 const [ formData , setFormData ] = useState < ProjectFormData > ( {
@@ -56,6 +77,12 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
5677 // Ref for the scrollable content container
5778 const contentRef = useRef < HTMLDivElement > ( null ) ;
5879
80+ // Wallet signing hooks
81+ const { signTransaction } = useWalletSigning ( ) ;
82+ const { requireWallet } = useWalletProtection ( {
83+ actionName : 'sign project creation transaction' ,
84+ } ) ;
85+
5986 // Reset scroll position when step changes with smooth transition
6087 useEffect ( ( ) => {
6188 if ( currentStep === 1 ) return ; // Skip for initial load
@@ -209,15 +236,83 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
209236 backup : contact . backupContact || '' ,
210237 } ,
211238 socialLinks : apiSocialLinks ,
239+ signer : 'GD4NCQMLAU5Z7HIMNGMKLSFLCA46AILVIB6FJZ7QEJXANFNGBHFR24H6' ,
212240 } ;
213241 } ;
214242
243+ const handleRetry = ( ) => {
244+ setSubmitErrors ( [ ] ) ;
245+ setFlowStep ( 'signing' ) ;
246+ } ;
247+
248+ const handleSignTransaction = async ( ) => {
249+ if ( ! unsignedTransaction || ! preparedData ) {
250+ setSubmitErrors ( [ 'No transaction to sign or prepared data missing' ] ) ;
251+ return ;
252+ }
253+
254+ requireWallet ( async ( ) => {
255+ setIsSigningTransaction ( true ) ;
256+ setFlowStep ( 'confirming' ) ;
257+
258+ try {
259+ // Sign the transaction using the wallet
260+ const signedXdr = await signTransaction ( unsignedTransaction ) ;
261+
262+ // Submit the signed transaction to the backend (Step 2)
263+ const confirmResponse = await confirmCrowdfundingProject ( {
264+ signedXdr,
265+ escrowAddress : preparedData . escrowAddress ,
266+ projectData : preparedData . projectData ,
267+ mappedMilestones : preparedData . mappedMilestones ,
268+ mappedTeam : preparedData . mappedTeam ,
269+ } ) ;
270+
271+ if ( confirmResponse . success ) {
272+ // Project created successfully
273+ setFlowStep ( 'success' ) ;
274+ setShowSuccess ( true ) ;
275+ setIsSigningTransaction ( false ) ;
276+ setIsSubmitting ( false ) ;
277+ } else {
278+ throw new Error (
279+ confirmResponse . message || 'Failed to create project'
280+ ) ;
281+ }
282+ } catch ( error ) {
283+ let errorMessage = 'Failed to sign transaction. Please try again.' ;
284+
285+ if ( error instanceof Error ) {
286+ if ( error . message . includes ( 'User rejected' ) ) {
287+ errorMessage =
288+ 'Transaction signing was cancelled. Please try again.' ;
289+ } else if ( error . message . includes ( 'Invalid transaction' ) ) {
290+ errorMessage =
291+ 'Invalid transaction format. Please contact support.' ;
292+ } else if ( error . message . includes ( 'Network' ) ) {
293+ errorMessage =
294+ 'Network error. Please check your connection and try again.' ;
295+ } else if ( error . message . includes ( 'Wallet not connected' ) ) {
296+ errorMessage =
297+ 'Wallet is not connected. Please reconnect your wallet.' ;
298+ } else {
299+ errorMessage = error . message ;
300+ }
301+ }
302+
303+ setSubmitErrors ( [ errorMessage ] ) ;
304+ setIsSigningTransaction ( false ) ;
305+ setFlowStep ( 'signing' ) ;
306+ }
307+ } ) ;
308+ } ;
309+
215310 const handleSubmit = async ( ) => {
216311 setIsSubmitting ( true ) ;
217312 setIsLoading ( true ) ;
313+ setFlowStep ( 'preparing' ) ;
218314
219315 try {
220- // Validate full payload with master schema before submitting
221316 const milestoneSchema = z
222317 . object ( {
223318 id : z . string ( ) . optional ( ) ,
@@ -301,6 +396,7 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
301396 parsed . error . issues . map ( i => `${ i . path . join ( '.' ) } - ${ i . message } ` )
302397 ) ;
303398 setIsSubmitting ( false ) ;
399+ setFlowStep ( 'form' ) ;
304400 return ;
305401 }
306402
@@ -309,25 +405,42 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
309405 // Map form data to API request format
310406 const apiRequest = mapFormDataToApiRequest ( payload ) ;
311407
312- // Call the API
313- const response = await createCrowdfundingProject ( apiRequest ) ;
408+ // Step 1: Prepare project and get unsigned transaction
409+ const prepareResponse = await prepareCrowdfundingProject ( apiRequest ) ;
314410
315- if ( response . success ) {
316- // Show success modal
411+ if ( prepareResponse . success ) {
412+ // Store prepared data for step 2
413+ setPreparedData ( prepareResponse . data ) ;
414+ setUnsignedTransaction ( prepareResponse . data . unsignedXdr ) ;
415+ setFlowStep ( 'signing' ) ;
317416 setIsLoading ( false ) ;
318- setShowSuccess ( true ) ;
319- setIsSubmitting ( false ) ;
320417 } else {
321- throw new Error ( response . message || 'Failed to create project' ) ;
418+ throw new Error ( prepareResponse . message || 'Failed to prepare project' ) ;
322419 }
323420 } catch ( error ) {
324- setSubmitErrors ( [
325- error instanceof Error
326- ? error . message
327- : 'Error submitting project. Please try again.' ,
328- ] ) ;
421+ let errorMessage = 'Error preparing project. Please try again.' ;
422+
423+ if ( error instanceof Error ) {
424+ if ( error . message . includes ( 'Network' ) ) {
425+ errorMessage =
426+ 'Network error. Please check your connection and try again.' ;
427+ } else if ( error . message . includes ( 'Validation' ) ) {
428+ errorMessage =
429+ 'Project validation failed. Please check your project details.' ;
430+ } else if ( error . message . includes ( 'Unauthorized' ) ) {
431+ errorMessage =
432+ 'Authentication required. Please log in and try again.' ;
433+ } else if ( error . message . includes ( 'Server' ) ) {
434+ errorMessage = 'Server error. Please try again in a few moments.' ;
435+ } else {
436+ errorMessage = error . message ;
437+ }
438+ }
439+
440+ setSubmitErrors ( [ errorMessage ] ) ;
329441 setIsLoading ( false ) ;
330442 setIsSubmitting ( false ) ;
443+ setFlowStep ( 'form' ) ;
331444 }
332445 } ;
333446
@@ -377,6 +490,11 @@ const CreateProjectModal = ({ open, setOpen }: CreateProjectModalProps) => {
377490 setCurrentStep ( 1 ) ;
378491 setShowSuccess ( false ) ;
379492 setIsLoading ( false ) ;
493+ setUnsignedTransaction ( null ) ;
494+ setIsSigningTransaction ( false ) ;
495+ setSubmitErrors ( [ ] ) ;
496+ setPreparedData ( null ) ;
497+ setFlowStep ( 'form' ) ;
380498 setOpen ( false ) ;
381499 } ;
382500
@@ -429,24 +547,24 @@ Our development is structured in clear phases with measurable milestones and com
429547 title : 'Smart Contract Development' ,
430548 description :
431549 'Develop and audit core smart contracts for yield farming and automated market making. This includes the main protocol contracts, token contracts, and governance mechanisms.' ,
432- startDate : '2024 -02-01' ,
433- endDate : '2024 -04-30' ,
550+ startDate : '2026 -02-01' ,
551+ endDate : '2026 -04-30' ,
434552 } ,
435553 {
436554 id : 'milestone-2' ,
437555 title : 'Frontend Development' ,
438556 description :
439557 'Build a user-friendly web interface for interacting with the protocol. This includes dashboard, yield farming interface, and portfolio management tools.' ,
440- startDate : '2024-03 -01' ,
441- endDate : '2024-05-31 ' ,
558+ startDate : '2026-05 -01' ,
559+ endDate : '2026-06-30 ' ,
442560 } ,
443561 {
444562 id : 'milestone-3' ,
445563 title : 'Security Audit & Testing' ,
446564 description :
447565 'Conduct comprehensive security audits, penetration testing, and bug bounty programs to ensure the protocol is secure and ready for mainnet launch.' ,
448- startDate : '2024-05 -01' ,
449- endDate : '2024-07 -31' ,
566+ startDate : '2026-07 -01' ,
567+ endDate : '2026-08 -31' ,
450568 } ,
451569 ] ,
452570 } ,
@@ -482,12 +600,37 @@ Our development is structured in clear phases with measurable milestones and com
482600 } ;
483601
484602 const renderStepContent = ( ) => {
485- if ( isLoading ) {
603+ // Handle the new two-step flow states
604+ if ( flowStep === 'preparing' || isLoading ) {
486605 return < LoadingScreen /> ;
487606 }
488- if ( showSuccess ) {
607+ if ( flowStep === 'success' || showSuccess ) {
489608 return < SuccessScreen onContinue = { handleReset } /> ;
490609 }
610+ if (
611+ flowStep === 'signing' &&
612+ unsignedTransaction &&
613+ ! isSigningTransaction
614+ ) {
615+ return (
616+ < TransactionSigningScreen
617+ onSign = { handleSignTransaction }
618+ flowStep = 'signing'
619+ onRetry = { handleRetry }
620+ hasError = { submitErrors . length > 0 }
621+ errorMessage = { submitErrors [ 0 ] }
622+ />
623+ ) ;
624+ }
625+ if ( flowStep === 'confirming' || isSigningTransaction ) {
626+ return (
627+ < TransactionSigningScreen
628+ onSign = { handleSignTransaction }
629+ isSigning = { true }
630+ flowStep = 'confirming'
631+ />
632+ ) ;
633+ }
491634
492635 switch ( currentStep ) {
493636 case 1 :
@@ -547,7 +690,7 @@ Our development is structured in clear phases with measurable milestones and com
547690 open = { open }
548691 setOpen = { setOpen }
549692 >
550- { ! ( showSuccess || isLoading ) && (
693+ { flowStep === 'form' && (
551694 < Header
552695 currentStep = { currentStep }
553696 onBack = { handleBack }
@@ -558,7 +701,7 @@ Our development is structured in clear phases with measurable milestones and com
558701 ref = { contentRef }
559702 className = { `min-h-[calc(55vh)] px-4 transition-opacity duration-100 md:px-[50px] lg:px-[75px] xl:px-[150px]` }
560703 >
561- { showSuccess || isLoading ? (
704+ { flowStep !== 'form' ? (
562705 < div className = 'flex h-full items-center justify-center' >
563706 { renderStepContent ( ) }
564707 </ div >
@@ -582,7 +725,7 @@ Our development is structured in clear phases with measurable milestones and com
582725 </ >
583726 ) }
584727 </ div >
585- { ! ( showSuccess || isLoading ) && (
728+ { flowStep === 'form' && (
586729 < Footer
587730 currentStep = { currentStep }
588731 onContinue = { handleContinue }
0 commit comments