diff --git a/app/globals.css b/app/globals.css index a5c68ecf0..e57eb72d1 100644 --- a/app/globals.css +++ b/app/globals.css @@ -173,3 +173,16 @@ button { .slim-scrollbar::-webkit-scrollbar-track { background-color: transparent; } + +/* Hide arrows in Chrome, Safari, Edge, Opera */ +input[type='number']::-webkit-inner-spin-button, +input[type='number']::-webkit-outer-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Hide arrows in Firefox */ +input[type='number'] { + -moz-appearance: textfield; + appearance: textfield; +} diff --git a/components/flows/back-project/back-project-form.tsx b/components/flows/back-project/back-project-form.tsx new file mode 100644 index 000000000..99b54750d --- /dev/null +++ b/components/flows/back-project/back-project-form.tsx @@ -0,0 +1,202 @@ +'use client'; + +import type React from 'react'; +import { useState } from 'react'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; +import { Checkbox } from '@/components/ui/checkbox'; +import { ArrowLeft, Check, Copy } from 'lucide-react'; +import { BoundlessButton } from '@/components/buttons'; + +interface BackProjectFormProps { + onSubmit: (data: { + amount: string; + currency: string; + token: string; + network: string; + walletAddress: string; + keepAnonymous: boolean; + }) => void; + isLoading?: boolean; +} + +const QUICK_AMOUNTS = [10, 20, 30, 50, 100, 500, 1000]; + +export function BackProjectForm({ + onSubmit, + isLoading = false, +}: BackProjectFormProps) { + const [amount, setAmount] = useState(''); + const [currency] = useState('USDT'); + const [token, setToken] = useState(''); + const [network, setNetwork] = useState('Stella / Soroban'); + const [walletAddress] = useState('GDS3...GB7'); + const [keepAnonymous, setKeepAnonymous] = useState(false); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + onSubmit({ + amount, + currency, + token, + network, + walletAddress, + keepAnonymous, + }); + }; + + const handleQuickAmount = (quickAmount: number) => { + setAmount(quickAmount.toString()); + }; + + const handleCopyAddress = async (e: React.MouseEvent) => { + e.preventDefault(); + try { + await navigator.clipboard.writeText(walletAddress); + // Could add toast notification here instead of state + } catch (err) { + // Fallback for browsers that don't support clipboard API + const textArea = document.createElement('textarea'); + console.error(err); + + textArea.value = walletAddress; + document.body.appendChild(textArea); + textArea.select(); + try { + document.execCommand('copy'); + } catch (copyErr) { + console.error('Failed to copy address:', copyErr); + } + document.body.removeChild(textArea); + } + }; + + const isFormValid = amount && currency && token && walletAddress; + + return ( +
+
+ +

Back Project

+
+
+
+

+ Funds will be held in escrow and released only upon milestone + approvals. +

+
+ +
+ +
+ {currency} + setAmount(e.target.value)} + type='number' + className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none' + placeholder='1000' + disabled={isLoading} + /> +
+

min. amount: $10

+ +
+ {QUICK_AMOUNTS.map(quickAmount => ( + + ))} +
+
+ +
+ + +
+ +
+ +
+ setNetwork(e.target.value)} + type='text' + className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none' + disabled={isLoading} + /> +
+
+ +
+ + + + {walletAddress} + + +
+ +
+ setKeepAnonymous(checked as boolean)} + disabled={isLoading} + className='border-stepper-border data-[state=checked]:bg-primary data-[state=checked]:border-primary' + /> + +
+ + + Confirm Contribution + +
+
+ ); +} diff --git a/components/flows/back-project/index.tsx b/components/flows/back-project/index.tsx new file mode 100644 index 000000000..972d5a0e9 --- /dev/null +++ b/components/flows/back-project/index.tsx @@ -0,0 +1,111 @@ +'use client'; + +import { useState } from 'react'; +import { BoundlessButton } from '@/components/buttons'; +import { ProjectSubmissionSuccess } from '@/components/project'; +import BoundlessSheet from '@/components/sheet/boundless-sheet'; +import { ProjectSubmissionLoading } from '@/components/flows/back-project/project-submission-loading'; +import { BackProjectForm } from './back-project-form'; + +type BackProjectState = 'form' | 'loading' | 'success'; + +interface BackProjectData { + amount: string; + currency: string; + token: string; + network: string; + walletAddress: string; + keepAnonymous: boolean; +} + +const BackProject = () => { + const [isSheetOpen, setIsSheetOpen] = useState(false); + const [backProjectState, setBackProjectState] = + useState('form'); + + const handleBackProject = (data: BackProjectData) => { + setBackProjectState('loading'); + console.log(data); + + // Simulate API call + setTimeout(() => { + setBackProjectState('success'); + }, 2000); + }; + + // const handleContinue = () => { + // setIsSheetOpen(false) + // setBackProjectState("form") + // } + + // const handleViewHistory = () => { + // // Navigate to history page or open history modal + // setIsSheetOpen(false) + // // TODO: Implement backing history modal or navigation + // } + + // const handleBack = () => { + // if (backProjectState === "success") { + // setBackProjectState("form") + // } + // } + + const renderSheetContent = () => { + if (backProjectState === 'success') { + return ( +
+
+ {/* */} +
+ +
+ ); + } + + return ( +
+ + + {backProjectState === 'loading' && ( +
+ +
+ )} +
+ ); + }; + + return ( +
+ + {renderSheetContent()} + + + setIsSheetOpen(true)}> + Back Project + +
+ ); +}; + +export default BackProject; diff --git a/components/flows/back-project/project-submission-loading.tsx b/components/flows/back-project/project-submission-loading.tsx new file mode 100644 index 000000000..4dc27ea2d --- /dev/null +++ b/components/flows/back-project/project-submission-loading.tsx @@ -0,0 +1,15 @@ +export function ProjectSubmissionLoading() { + return ( +
+
+ {/* Outer spinning ring */} +
+ {/* Inner spinning arc */} +
+
+

+ Processing your contribution... +

+
+ ); +} diff --git a/components/project/ProjectSubmissionSuccess.tsx b/components/project/ProjectSubmissionSuccess.tsx index cc3683512..fa2e2e31c 100644 --- a/components/project/ProjectSubmissionSuccess.tsx +++ b/components/project/ProjectSubmissionSuccess.tsx @@ -1,26 +1,46 @@ import Image from 'next/image'; +import Link from 'next/link'; import React from 'react'; -function ProjectSubmissionSuccess() { +interface ProjectSubmissionSuccessProps { + title?: string; + description?: string; + linkSection?: string; + linkName?: string; + url?: string; + continueAction?: () => void; +} + +function ProjectSubmissionSuccess({ + title = 'Project Submitted!', + description = 'Your project has been submitted and is now under admin review. You’ll receive an update within 72 hours. Once approved, your project will proceed to public validation.', + linkSection = 'You can track the status of your submission anytime on the', + linkName = 'Projects page.', + url = '/projects', + continueAction, +}: ProjectSubmissionSuccessProps) { return (
-
Project Submitted!
-
+
{title}
+
done
-
+

- Your project has been submitted and is now under admin review. You’ll - receive an update within 72 hours. Once approved, your project will - proceed to public validation. + {description}

-

- You can track the status of your submission anytime on the{' '} - Projects page. +

+ {linkSection}{' '} + + {linkName} +

-
diff --git a/lib/data/backing-history-mock.ts b/lib/data/backing-history-mock.ts new file mode 100644 index 000000000..3de9417e2 --- /dev/null +++ b/lib/data/backing-history-mock.ts @@ -0,0 +1,142 @@ +import type { BackingHistoryItem } from '@/types/backing-history'; + +export const mockBackingHistory: BackingHistoryItem[] = [ + { + id: '1', + backer: { + name: 'Collins Odumeje', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'GDS3...GB7', + }, + amount: 2300, + currency: 'USDT', + date: new Date('2025-08-17'), + timeAgo: '3s', + }, + { + id: '2', + backer: { + name: 'Sarah Chen', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'ABC1...XYZ', + }, + amount: 1500, + currency: 'USDT', + date: new Date('2025-08-16'), + timeAgo: '1d', + }, + { + id: '3', + backer: { + name: 'Anonymous', + isAnonymous: true, + avatar: '/anonymous-user-concept.png', + walletAddress: 'DEF4...789', + }, + amount: 5000, + currency: 'USDT', + date: new Date('2025-08-15'), + timeAgo: '2d', + }, + { + id: '4', + backer: { + name: 'Michael Rodriguez', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'HIJ7...456', + }, + amount: 750, + currency: 'USDT', + date: new Date('2025-08-14'), + timeAgo: '3d', + }, + { + id: '5', + backer: { + name: 'Anonymous', + isAnonymous: true, + avatar: '/anonymous-user-concept.png', + walletAddress: 'KLM0...123', + }, + amount: 3200, + currency: 'USDT', + date: new Date('2025-08-13'), + timeAgo: '4d', + }, + { + id: '6', + backer: { + name: 'Emma Thompson', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'NOP3...890', + }, + amount: 1800, + currency: 'USDT', + date: new Date('2025-08-12'), + timeAgo: '5d', + }, + { + id: '7', + backer: { + name: 'David Kim', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'QRS6...567', + }, + amount: 4500, + currency: 'USDT', + date: new Date('2025-08-11'), + timeAgo: '6d', + }, + { + id: '8', + backer: { + name: 'Anonymous', + isAnonymous: true, + avatar: '/anonymous-user-concept.png', + walletAddress: 'TUV9...234', + }, + amount: 950, + currency: 'USDT', + date: new Date('2025-08-10'), + timeAgo: '1w', + }, + { + id: '9', + backer: { + name: 'Lisa Wang', + isAnonymous: false, + avatar: '/diverse-user-avatars.png', + walletAddress: 'WXY2...901', + }, + amount: 2750, + currency: 'USDT', + date: new Date('2025-08-09'), + timeAgo: '1w', + }, + { + id: '10', + backer: { + name: 'Anonymous', + isAnonymous: true, + avatar: '/anonymous-user-concept.png', + walletAddress: 'ZAB5...678', + }, + amount: 6200, + currency: 'USDT', + date: new Date('2025-08-08'), + timeAgo: '1w', + }, +]; + +export const sortOptions = [ + { value: 'newest', label: 'Newest first' }, + { value: 'oldest', label: 'Oldest first' }, + { value: 'alphabetical', label: 'Alphabetical' }, + { value: 'amount-high', label: 'Highest first' }, + { value: 'amount-low', label: 'Lowest first' }, +]; diff --git a/types/backing-history.ts b/types/backing-history.ts new file mode 100644 index 000000000..054c4506b --- /dev/null +++ b/types/backing-history.ts @@ -0,0 +1,32 @@ +export interface BackingHistoryItem { + id: string; + backer: { + name: string; + isAnonymous: boolean; + avatar?: string; + walletAddress: string; + }; + amount: number; + currency: string; + date: Date; + timeAgo: string; +} + +export interface BackingHistoryFilters { + searchQuery: string; + sortBy: 'newest' | 'oldest' | 'alphabetical' | 'amount-high' | 'amount-low'; + dateRange: { + from: Date | null; + to: Date | null; + }; + amountRange: { + min: number; + max: number; + }; + identityType: 'all' | 'identified' | 'anonymous'; +} + +export interface BackingHistorySortOption { + value: string; + label: string; +}