diff --git a/components/campaigns/LaunchCampaignFlow.tsx b/components/campaigns/LaunchCampaignFlow.tsx new file mode 100644 index 000000000..353e805ec --- /dev/null +++ b/components/campaigns/LaunchCampaignFlow.tsx @@ -0,0 +1,734 @@ +'use client'; + +import React, { useState } from 'react'; + +import { Input } from '@/components/ui/input'; +import { Textarea } from '@/components/ui/textarea'; +import { Badge } from '@/components/ui/badge'; +import { + Calendar, + X, + Package, + DollarSign, + ImagePlus, + ArrowLeft, +} from 'lucide-react'; +import Image from 'next/image'; +import { cn } from '@/lib/utils'; +import { + Select, + SelectItem, + SelectContent, + SelectValue, + SelectTrigger, +} from '@/components/ui/select'; +import { BoundlessButton } from '../buttons'; +import { Checkbox } from '../ui/checkbox'; + +interface LaunchCampaignFlowProps { + onComplete: () => void; +} + +const LaunchCampaignFlow: React.FC = ({ + onComplete, +}) => { + const [currentPhase, setCurrentPhase] = useState<'details' | 'escrow'>( + 'details' + ); + const [formData, setFormData] = useState({ + title: 'Boundless', + description: + 'Boundless is a trustless, decentralized application (dApp) that empowers changemakers and builders to raise funds transparently without intermediaries. Campaigns are structured around clearly defined milestones, with funds held in escrow and released only upon approval. Grant creators can create campaigns with defined milestones and funding goals.', + fundingGoal: '123,000.00', + category: '', + images: [] as string[], + duration: '90 Days', + }); + const [selectedTags, setSelectedTags] = useState([ + 'Web3', + 'Crowdfunding', + ]); + const [escrowData, setEscrowData] = useState({ + network: 'Stella / Soroban', + transactionType: 'On-chain, irreversible', + walletAddress: '', + agreedToTerms: false, + }); + + const milestones = [ + { + id: '1', + title: 'Prototype & Smart Contract Setup', + description: + 'Develop a functional UI prototype for the crowdfunding and grant flow. Simultaneously, implement and test Soroban smart contracts for escrow logic, milestone validation, and secure fund handling.', + deliveryDate: 'October 10, 2025', + fundAmount: 29000, + isExpanded: true, + }, + { + id: '2', + title: 'Campaign & Grant Builder Integration', + description: + 'Integrate campaign creation tools and grant builder functionality.', + deliveryDate: 'November 15, 2025', + fundAmount: 43050, + isExpanded: false, + }, + { + id: '3', + title: 'Platform Launch & Community Building', + description: + 'Launch the platform to the public and build a strong community.', + deliveryDate: 'December 20, 2025', + fundAmount: 49200, + isExpanded: false, + }, + ]; + + const escrowTerms = [ + { + title: 'Smart Contract Control', + description: + 'All funds contributed to your campaign will be held in a smart contract powered by Soroban.', + }, + { + title: 'Milestone-Based Release', + description: + 'Funds will only be released upon successful completion and approval of individual milestones as defined in your campaign.', + }, + { + title: 'Immutable Fund Allocation', + description: + 'Once the campaign is submitted to escrow, your milestone structure and fund percentages cannot be modified.', + }, + { + title: 'Non-custodial Holding', + description: + 'Boundless does not hold your funds. Escrow is fully decentralized and governed by the smart contract.', + }, + { + title: 'Unmet Funding Goal', + description: + 'If the campaign goal is not reached by the deadline, no funds will be released and contributors may be refunded (depending on platform policy).', + }, + { + title: 'KYC Compliance', + description: + 'You must maintain a verified KYC status throughout the campaign to remain eligible for fund disbursement.', + }, + { + title: 'Transparent', + description: + 'All fund flows and milestone reviews are publicly visible for accountability.', + }, + ]; + + const handleInputChange = (field: string, value: string) => { + setFormData(prev => ({ ...prev, [field]: value })); + }; + + const handleTagToggle = (tag: string) => { + setSelectedTags(prev => + prev.includes(tag) ? prev.filter(t => t !== tag) : [...prev, tag] + ); + }; + + const handleImageUpload = (event: React.ChangeEvent) => { + const files = event.target.files; + if (files && files.length > 0) { + const validFiles = Array.from(files).filter( + file => file.type.startsWith('image/') && file.size <= 5 * 1024 * 1024 // 5MB limit + ); + + if (validFiles.length !== files.length) { + // console.warn( + // 'Some files were skipped - only images under 5MB are allowed' + // ); + } + + const newImages = validFiles.map(file => URL.createObjectURL(file)); + setFormData(prev => ({ + ...prev, + images: [...prev.images, ...newImages].slice(0, 4), // Limit to 4 images + })); + } + }; + + const removeImage = (index: number) => { + setFormData(prev => ({ + ...prev, + images: prev.images.filter((_, i) => i !== index), + })); + }; + + const isFormValid = (): boolean => { + return Boolean( + formData.title && + formData.description && + formData.fundingGoal && + formData.images.length > 0 && + formData.duration + ); + }; + + const isEscrowValid = (): boolean => { + return Boolean(escrowData.walletAddress && escrowData.agreedToTerms); + }; + + const handleNextPhase = () => { + if (currentPhase === 'details') { + setCurrentPhase('escrow'); + } + }; + + const handleBackPhase = () => { + if (currentPhase === 'escrow') { + setCurrentPhase('details'); + } + }; + + const phases = [ + { + title: 'Campaign Details', + state: + currentPhase === 'details' + ? ('active' as const) + : ('completed' as const), + }, + { + title: 'Escrow Setup', + state: + currentPhase === 'escrow' ? ('active' as const) : ('pending' as const), + }, + ]; + + return ( +
+ {/* Header with Progress Steps */} +
+

+ {currentPhase === 'details' ? 'Set Campaign Details' : 'Escrow Setup'} +

+
+
+ {phases.map((phase, index) => ( +
+
+
+
+
+ ))} +
+
+
+ + {currentPhase === 'details' ? ( + + ) : ( + + )} +
+ ); +}; + +// Campaign Details Form Component +const CampaignDetailsForm: React.FC<{ + formData: any; + selectedTags: string[]; + onInputChange: (field: string, value: string) => void; + onTagToggle: (tag: string) => void; + onImageUpload: (event: React.ChangeEvent) => void; + onRemoveImage: (index: number) => void; + isFormValid: () => boolean; + onNext: () => void; +}> = ({ + formData, + selectedTags, + onInputChange, + onTagToggle, + onImageUpload, + onRemoveImage, + isFormValid, + onNext, +}) => { + const [tagQuery, setTagQuery] = useState(''); + const [isSuggestionsOpen, setIsSuggestionsOpen] = useState(false); + + const projectTags = [ + { value: 'web3', label: 'Web3' }, + { value: 'crowdfunding', label: 'Crowdfunding' }, + { value: 'defi', label: 'DeFi' }, + { value: 'nft', label: 'NFT' }, + { value: 'dao', label: 'DAO' }, + { value: 'blockchain', label: 'Blockchain' }, + { value: 'cryptocurrency', label: 'Cryptocurrency' }, + { value: 'smart-contracts', label: 'Smart Contracts' }, + { value: 'dapp', label: 'dApp' }, + { value: 'metaverse', label: 'Metaverse' }, + { value: 'gaming', label: 'Gaming' }, + { value: 'social-impact', label: 'Social Impact' }, + { value: 'sustainability', label: 'Sustainability' }, + { value: 'education', label: 'Education' }, + { value: 'healthcare', label: 'Healthcare' }, + { value: 'finance', label: 'Finance' }, + { value: 'art', label: 'Art' }, + { value: 'music', label: 'Music' }, + { value: 'sports', label: 'Sports' }, + { value: 'technology', label: 'Technology' }, + ]; + + const handleAddTag = (tag: string) => { + const trimmedTag = tag.trim(); + if (trimmedTag && !selectedTags.includes(trimmedTag)) { + onTagToggle(trimmedTag); + } + setTagQuery(''); + setIsSuggestionsOpen(false); + }; + + const handleRemoveTag = (tag: string) => { + onTagToggle(tag); + }; + return ( +
+ {/* Campaign Title */} +
+ +
+ + onInputChange('title', e.target.value)} + type='text' + className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none' + placeholder='Enter campaign title' + /> +
+
+ +
+ +