diff --git a/apps/frontend/app/escrow/create/page.tsx b/apps/frontend/app/escrow/create/page.tsx new file mode 100644 index 0000000..c161af6 --- /dev/null +++ b/apps/frontend/app/escrow/create/page.tsx @@ -0,0 +1,20 @@ +import CreateEscrowWizard from '@/component/escrow/CreateEscrowWizard'; + +export default function CreateEscrowPage() { + return ( +
+
+
+

+ Create New Escrow +

+

+ Set up a secure escrow agreement in just a few steps. +

+
+ + +
+
+ ); +} diff --git a/apps/frontend/component/escrow/CreateEscrowWizard.tsx b/apps/frontend/component/escrow/CreateEscrowWizard.tsx new file mode 100644 index 0000000..c07b0e5 --- /dev/null +++ b/apps/frontend/component/escrow/CreateEscrowWizard.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { useState } from 'react'; +import { useForm, FormProvider } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import * as z from 'zod'; +import { createEscrowSchema, CreateEscrowFormData } from '@/lib/escrow-schema'; +import BasicInfoStep from './create/BasicInfoStep'; +import PartiesStep from './create/PartiesStep'; +import TermsStep from './create/TermsStep'; +import ReviewStep from './create/ReviewStep'; +import { CheckCircle2, ChevronRight, ChevronLeft, Loader2, AlertCircle } from 'lucide-react'; +import { isConnected, signTransaction, getAddress } from '@stellar/freighter-api'; +import { Horizon, Networks, TransactionBuilder, Account, Asset, Operation } from 'stellar-sdk'; + +const STEPS = [ + { id: 'basic', title: 'Basic Info', fields: ['title', 'description', 'category'] }, + { id: 'parties', title: 'Parties', fields: ['counterpartyAddress'] }, + { id: 'terms', title: 'Terms', fields: ['amount', 'deadline', 'asset'] }, + { id: 'review', title: 'Review', fields: [] }, +]; + +export default function CreateEscrowWizard() { + const [currentStep, setCurrentStep] = useState(0); + const [isSubmitting, setIsSubmitting] = useState(false); + const [txHash, setTxHash] = useState(null); + const [submitError, setSubmitError] = useState(null); + + const methods = useForm({ + resolver: zodResolver(createEscrowSchema), + mode: 'onChange', + defaultValues: { + asset: 'XLM', + } + }); + + const { trigger, handleSubmit, getValues } = methods; + + const nextStep = async () => { + const fields = STEPS[currentStep].fields as any[]; + const isValid = await trigger(fields); + + if (isValid) { + setCurrentStep((prev) => Math.min(prev + 1, STEPS.length - 1)); + setSubmitError(null); + } + }; + + const prevStep = () => { + setCurrentStep((prev) => Math.max(prev - 1, 0)); + setSubmitError(null); + }; + + const onSubmit = async (data: CreateEscrowFormData) => { + setIsSubmitting(true); + setSubmitError(null); + + try { + // 1. Check Wallet Connection + const connected = await isConnected(); + if (!connected) { + throw new Error('Freighter wallet not connected. Please install and connect Freighter.'); + } + + const { address } = await getAddress(); + if (!address) { + throw new Error('Could not retrieve address from Freighter.'); + } + + // 2. Build Transaction (Mock/Placeholder logic) + // In a real app, you would fetch the sequence number, build the invokeHostFunction op, etc. + // For this demo, we'll demonstrate the intent. + + // Example: + // const server = new Horizon.Server('https://horizon-testnet.stellar.org'); + // const account = await server.loadAccount(publicKey); + // const tx = new TransactionBuilder(account, { + // fee: '100', + // networkPassphrase: Networks.TESTNET, + // }) + // .addOperation(...) // Invoke contract logic here + // .setTimeout(30) + // .build(); + + // Since we don't have the contract bindings generated, we'll simulate the delay and signing request + // to demonstrate the UX flow. + + // await signTransaction(tx.toXDR(), { network: 'TESTNET' }); + + await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate building + + // Simulate signing success + // const signedXdr = await signTransaction(mockXdr, ...); + + // Simulate submission + // await server.submitTransaction(transaction); + + setTxHash('7a8b9c...mock_hash...1d2e3f'); // Success state + + } catch (error: any) { + console.error(error); + setSubmitError(error.message || 'Failed to create escrow. Please try again.'); + } finally { + setIsSubmitting(false); + } + }; + + if (txHash) { + return ( +
+
+ +
+

Escrow Created Successfully!

+

+ Your escrow agreement has been deployed to the network. +

+
+

Transaction Hash

+

{txHash}

+
+
+ + Return to Dashboard + +
+
+ ); + } + + return ( +
+
+ {/* Progress Indicator */} + + + {/* Main Content */} + +
+ + {/* Steps */} +
+ {currentStep === 0 && } + {currentStep === 1 && } + {currentStep === 2 && } + {currentStep === 3 && } +
+ + {/* Error Message */} + {submitError && ( +
+ +

{submitError}

+
+ )} + + {/* Navigation Buttons */} +
+ + + {currentStep === STEPS.length - 1 ? ( + + ) : ( + + )} +
+
+
+
+
+ ); +} diff --git a/apps/frontend/component/escrow/create/BasicInfoStep.tsx b/apps/frontend/component/escrow/create/BasicInfoStep.tsx new file mode 100644 index 0000000..51d8eef --- /dev/null +++ b/apps/frontend/component/escrow/create/BasicInfoStep.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { useFormContext } from 'react-hook-form'; +import { CreateEscrowFormData } from '@/lib/escrow-schema'; +import Input from '../../ui/Input'; +import TextArea from '../../ui/TextArea'; +import Select from '../../ui/Select'; + +export default function BasicInfoStep() { + const { + register, + formState: { errors }, + } = useFormContext(); + + return ( +
+
+

Basic Information

+

+ Start by providing the basic details about this escrow agreement. +

+ + {/* Title Field */} + + + {/* Category Field */} + + + {/* Description Field */} +