diff --git a/app/user/backing-history/page.tsx b/app/user/backing-history/page.tsx index bb21f5a66..b42193df7 100644 --- a/app/user/backing-history/page.tsx +++ b/app/user/backing-history/page.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { Button } from '@/components/ui/button'; -import BackingHistory from '@/components/flows/backing-history/Index'; +import BackingHistory from '@/components/flows/backing-history/index'; // Sample data matching the images const sampleBackers = [ diff --git a/components/project/MilestoneSubmissionModal.tsx b/components/project/MilestoneSubmissionModal.tsx new file mode 100644 index 000000000..010e97ce1 --- /dev/null +++ b/components/project/MilestoneSubmissionModal.tsx @@ -0,0 +1,379 @@ +'use client'; + +import React, { useState } from 'react'; +import BoundlessSheet from '../sheet/boundless-sheet'; +import { BoundlessButton } from '../buttons/BoundlessButton'; +import { Label } from '../ui/label'; +import { + Calendar, + Coins, + Link as LinkIcon, + ChevronUp, + CloudUpload, + Check, + Trash2, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import MilestoneSubmissionSuccess from './MilestoneSubmissionSuccess'; + +export interface MilestoneSubmissionData { + files: File[]; + externalLinks: string[]; +} + +interface MilestoneSubmissionModalProps { + open: boolean; + setOpen: (open: boolean) => void; + milestone: { + id: string; + title: string; + description: string; + deliveryDate: string; + fundAmount: number; + status: 'ready' | 'pending' | 'completed' | 'failed'; + }; + onSubmit: (data: MilestoneSubmissionData) => void; + loading?: boolean; +} + +const MilestoneSubmissionModal: React.FC = ({ + open, + setOpen, + milestone, + onSubmit, + loading = false, +}) => { + const [files, setFiles] = useState([]); + const [externalLinks, setExternalLinks] = useState(['www.']); + const [isExpanded, setIsExpanded] = useState(true); + const [focusedInput, setFocusedInput] = useState(null); + const [showSuccess, setShowSuccess] = useState(false); + + const handleFileUpload = (event: React.ChangeEvent) => { + const selectedFiles = Array.from(event.target.files || []); + setFiles(prev => [...prev, ...selectedFiles]); + }; + + const handleRemoveFile = (index: number) => { + setFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const handleExternalLinkChange = (index: number, value: string) => { + const newLinks = [...externalLinks]; + // Ensure the value starts with "www." + if (!value.startsWith('www.')) { + newLinks[index] = 'www.' + value; + } else { + newLinks[index] = value; + } + setExternalLinks(newLinks); + }; + + const handleSaveLink = () => { + // Add a new field with "www." + setExternalLinks(prev => [...prev, 'www.']); + }; + + const handleSubmit = () => { + const filteredLinks = externalLinks.filter(link => link.trim() !== ''); + onSubmit({ + files, + externalLinks: filteredLinks, + }); + setShowSuccess(true); + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount); + }; + + return ( + + {showSuccess ? ( + { + setShowSuccess(false); + setOpen(false); + }} + /> + ) : ( +
+ {/* Milestone Header */} +
+
+

+ Milestone Summary +

+

Milestone 1

+
+
+ + {/* Milestone Card - Centered */} +
+
+
+

+ {milestone.title} +

+
+ + {milestone.status.charAt(0).toUpperCase() + + milestone.status.slice(1)} + + +
+
+ + {isExpanded && ( + <> +

+ {milestone.description} +

+ +
+
+ + 1 {formatDate(milestone.deliveryDate)} +
+
+ + {formatCurrency(milestone.fundAmount)} +
+
+ + )} +
+
+ + {/* File Upload Section */} +
+
+

+ Upload Proof of Completion +

+ +
+ +
+ +
+
+
+ +
+ +
+

Upload your files

+

+ JPEG, PNG, PDF, Docs • Max. 20MB +

+
+
+ + + document.getElementById('file-upload')?.click() + } + > + Upload + + + +
+ + {/* Uploaded Files Section */} + {files.length > 0 && ( +
+ {/* Header with count */} +
+

Uploaded Files

+
+ + {files.length} + +
+
+ + {/* Files List */} +
+ {files.map((file, index) => ( +
+
+ {/* Green check circle */} +
+ +
+ + {/* File info */} +
+ + {file.name} + +
+ + Upload complete + + + + {(file.size / (1024 * 1024)).toFixed(1)}MB + +
+
+
+ + {/* Red trash button */} + +
+ ))} +
+
+ )} +
+
+ + {/* External Links Section */} +
+
+ + + {externalLinks.map((link, index) => ( +
+
+ + + handleExternalLinkChange(index, e.target.value) + } + onFocus={() => setFocusedInput(index)} + onBlur={() => setFocusedInput(null)} + style={{ + width: '500px', + height: '48px', + borderRadius: '12px', + borderWidth: '1px', + borderStyle: 'solid', + borderColor: + focusedInput === index ? '#A7F950' : '#2B2B2B', + backgroundColor: '#1C1C1C', + color: '#FFFFFF', + paddingLeft: '40px', + paddingRight: + link.trim() && link !== 'www.' ? '80px' : '16px', + paddingTop: '16px', + paddingBottom: '16px', + gap: '12px', + opacity: 1, + outline: 'none', + transition: 'border-color 0.2s ease', + }} + className='placeholder:text-gray-400' + /> + + {/* Save button - only show when there's more text than just "www." */} + {link.trim() && link !== 'www.' && ( + + Save + + )} +
+
+ ))} +
+
+ + {/* Submit Button */} +
+ + {loading ? 'Submitting...' : 'Submit'} + +
+
+ )} +
+ ); +}; + +export default MilestoneSubmissionModal; diff --git a/components/project/MilestoneSubmissionPage.tsx b/components/project/MilestoneSubmissionPage.tsx new file mode 100644 index 000000000..33cbabe0a --- /dev/null +++ b/components/project/MilestoneSubmissionPage.tsx @@ -0,0 +1,399 @@ +'use client'; + +import React, { useState } from 'react'; +import { BoundlessButton } from '../buttons/BoundlessButton'; +import { Input } from '../ui/input'; +import { Label } from '../ui/label'; +import { Textarea } from '../ui/textarea'; +import { + Calendar, + Coins, + Upload, + Link as LinkIcon, + ChevronUp, + ArrowLeft, +} from 'lucide-react'; +import { cn } from '@/lib/utils'; +import { MilestoneSubmissionData } from './MilestoneSubmissionModal'; + +interface MilestoneSubmissionPageProps { + milestone: { + id: string; + title: string; + description: string; + deliveryDate: string; + fundAmount: number; + status: 'ready' | 'pending' | 'completed' | 'failed'; + }; + onSubmit: (data: MilestoneSubmissionData) => void; + onBack: () => void; + loading?: boolean; +} + +const MilestoneSubmissionPage: React.FC = ({ + milestone, + onSubmit, + onBack, + loading = false, +}) => { + const [files, setFiles] = useState([]); + const [externalLinks, setExternalLinks] = useState(['']); + const [isExpanded, setIsExpanded] = useState(true); + const [additionalNotes, setAdditionalNotes] = useState(''); + + const handleFileUpload = (event: React.ChangeEvent) => { + const selectedFiles = Array.from(event.target.files || []); + setFiles(prev => [...prev, ...selectedFiles]); + }; + + const handleRemoveFile = (index: number) => { + setFiles(prev => prev.filter((_, i) => i !== index)); + }; + + const handleExternalLinkChange = (index: number, value: string) => { + const newLinks = [...externalLinks]; + newLinks[index] = value; + setExternalLinks(newLinks); + }; + + const handleAddExternalLink = () => { + setExternalLinks(prev => [...prev, '']); + }; + + const handleRemoveExternalLink = (index: number) => { + if (externalLinks.length > 1) { + setExternalLinks(prev => prev.filter((_, i) => i !== index)); + } + }; + + const handleSubmit = () => { + const filteredLinks = externalLinks.filter(link => link.trim() !== ''); + onSubmit({ + files, + externalLinks: filteredLinks, + }); + }; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + }); + }; + + const formatCurrency = (amount: number) => { + return new Intl.NumberFormat('en-US', { + style: 'currency', + currency: 'USD', + }).format(amount); + }; + + return ( +
+
+ {/* Header */} +
+ + + +
+

+ Milestone Submission +

+

+ Submit proof of completion for your milestone +

+
+
+ +
+ {/* Main Content */} +
+ {/* Milestone Card */} +
+
+

+ {milestone.title} +

+
+ + {milestone.status.charAt(0).toUpperCase() + + milestone.status.slice(1)} + + +
+
+ + {isExpanded && ( + <> +

+ {milestone.description} +

+ +
+
+ + 1 {formatDate(milestone.deliveryDate)} +
+
+ + {formatCurrency(milestone.fundAmount)} +
+
+ + )} +
+ + {/* File Upload Section */} +
+
+ +

+ Upload files that demonstrate the completion of this milestone +

+
+ +
+
+
+ +
+

+ Upload your files +

+

+ JPEG, PNG, PDF, Docs • Max. 20MB +

+
+
+ + document.getElementById('file-upload-page')?.click() + } + > + Upload + +
+ + +
+ + {/* Uploaded Files List */} + {files.length > 0 && ( +
+

Uploaded Files

+ {files.map((file, index) => ( +
+
+ +
+ + {file.name} + +

+ {(file.size / 1024 / 1024).toFixed(2)} MB +

+
+
+ +
+ ))} +
+ )} +
+ + {/* External Links Section */} +
+
+ +

+ Add links to external resources, demos, or documentation +

+
+ + {externalLinks.map((link, index) => ( +
+
+ + + handleExternalLinkChange(index, e.target.value) + } + className='pl-12 h-12 bg-[#0F0F0F] border-[#2B2B2B] text-white placeholder:text-gray-400 text-base' + /> +
+ {externalLinks.length > 1 && ( + + )} +
+ ))} + + + Add Another Link + +
+ + {/* Additional Notes Section */} +
+
+ +

+ Any additional context or explanations about your submission +

+
+ +