Skip to content

Commit d60957d

Browse files
authored
Link history flow (#146)
* feat: initialize back project flow and project history demo page * fix: implement back project form * fix: fix back project flow * feat: backing history * fix: link backing history flow
1 parent 4fb06a2 commit d60957d

11 files changed

Lines changed: 894 additions & 223 deletions

File tree

app/user/backing-history/page.tsx

Lines changed: 0 additions & 109 deletions
This file was deleted.

components/campaigns/CampaignTable.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import {
2525
TabFilter,
2626
mockApiService,
2727
} from '@/lib/data/campaigns-mock';
28+
import BackingHistory from './backing-history';
29+
import { sampleBackers } from '@/lib/data/backing-history-mock';
2830

2931
const CampaignRow = ({
3032
campaign,
@@ -420,6 +422,7 @@ const CampaignTable = () => {
420422
const [loading, setLoading] = useState(true);
421423
const [error, setError] = useState<string | null>(null);
422424
const [campaignSummaryOpen, setCampaignSummaryOpen] = useState(false);
425+
const [backingHistoryOpen, setBackingHistoryOpen] = useState(false);
423426
const [currentPage, setCurrentPage] = useState(1);
424427
const [totalPages, setTotalPages] = useState(1);
425428
const itemsPerPage = 10;
@@ -477,8 +480,8 @@ const CampaignTable = () => {
477480
setCampaignSummaryOpen(true);
478481
break;
479482
case 'view-history':
480-
// TODO: Navigate to history page
481483
toast.info('Opening history...');
484+
setBackingHistoryOpen(true);
482485
break;
483486
case 'campaign-details':
484487
// TODO: Navigate to details page
@@ -719,6 +722,11 @@ const CampaignTable = () => {
719722
open={campaignSummaryOpen}
720723
setOpen={setCampaignSummaryOpen}
721724
/>
725+
<BackingHistory
726+
open={backingHistoryOpen}
727+
setOpen={setBackingHistoryOpen}
728+
backers={sampleBackers}
729+
/>
722730
</div>
723731
);
724732
};
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
'use client';
2+
3+
import type React from 'react';
4+
import { useState } from 'react';
5+
import {
6+
Select,
7+
SelectContent,
8+
SelectItem,
9+
SelectTrigger,
10+
SelectValue,
11+
} from '@/components/ui/select';
12+
import { Checkbox } from '@/components/ui/checkbox';
13+
import { ArrowLeft, Check, Copy } from 'lucide-react';
14+
import { BoundlessButton } from '@/components/buttons';
15+
16+
interface BackProjectFormProps {
17+
onSubmit: (data: {
18+
amount: string;
19+
currency: string;
20+
token: string;
21+
network: string;
22+
walletAddress: string;
23+
keepAnonymous: boolean;
24+
}) => void;
25+
isLoading?: boolean;
26+
}
27+
28+
const QUICK_AMOUNTS = [10, 20, 30, 50, 100, 500, 1000];
29+
30+
export function BackProjectForm({
31+
onSubmit,
32+
isLoading = false,
33+
}: BackProjectFormProps) {
34+
const [amount, setAmount] = useState('');
35+
const [currency] = useState('USDT');
36+
const [token, setToken] = useState('');
37+
const [network, setNetwork] = useState('Stella / Soroban');
38+
const [walletAddress] = useState('GDS3...GB7');
39+
const [keepAnonymous, setKeepAnonymous] = useState(false);
40+
41+
const handleSubmit = (e: React.FormEvent) => {
42+
e.preventDefault();
43+
onSubmit({
44+
amount,
45+
currency,
46+
token,
47+
network,
48+
walletAddress,
49+
keepAnonymous,
50+
});
51+
};
52+
53+
const handleQuickAmount = (quickAmount: number) => {
54+
setAmount(quickAmount.toString());
55+
};
56+
57+
const handleCopyAddress = async (e: React.MouseEvent) => {
58+
e.preventDefault();
59+
try {
60+
await navigator.clipboard.writeText(walletAddress);
61+
// Could add toast notification here instead of state
62+
} catch {
63+
// Fallback for browsers that don't support clipboard API
64+
const textArea = document.createElement('textarea');
65+
// Silent fallback for older browsers
66+
textArea.value = walletAddress;
67+
document.body.appendChild(textArea);
68+
textArea.select();
69+
try {
70+
document.execCommand('copy');
71+
} catch {
72+
// Copy failed, but no need to log in production
73+
}
74+
document.body.removeChild(textArea);
75+
}
76+
};
77+
78+
const isFormValid = amount && currency && token && walletAddress;
79+
80+
return (
81+
<div className='text-white'>
82+
<div className='mb-5 flex gap-10 '>
83+
<ArrowLeft className='cursor-pointer' />
84+
<h1 className='text-white text-lg font-semibold'>Back Project</h1>
85+
</div>
86+
<div className='w-[500px] flex flex-col gap-3 pt-3 pb-6'>
87+
<div className='space-y-2'>
88+
<p className='text-orange-400 text-sm'>
89+
Funds will be held in escrow and released only upon milestone
90+
approvals.
91+
</p>
92+
</div>
93+
94+
<div className='flex flex-col gap-1'>
95+
<label className='text-xs text-card font-medium'>
96+
Amount <span className='text-red-500'>*</span>
97+
</label>
98+
<div className='w-full h-12 flex items-center gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border'>
99+
<span className='text-card text-sm'>{currency}</span>
100+
<input
101+
value={amount}
102+
onChange={e => setAmount(e.target.value)}
103+
type='number'
104+
className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none'
105+
placeholder='1000'
106+
disabled={isLoading}
107+
/>
108+
</div>
109+
<p className='text-card/60 text-xs'>min. amount: $10</p>
110+
111+
<div className='flex flex-wrap gap-2 mt-2'>
112+
{QUICK_AMOUNTS.map(quickAmount => (
113+
<button
114+
key={quickAmount}
115+
type='button'
116+
onClick={() => handleQuickAmount(quickAmount)}
117+
className='px-3 py-1 text-sm bg-stepper-foreground border border-stepper-border text-card rounded-[8px] hover:bg-stepper-foreground/80 transition-colors'
118+
disabled={isLoading}
119+
>
120+
${quickAmount}
121+
</button>
122+
))}
123+
</div>
124+
</div>
125+
126+
<div className='flex flex-col gap-1'>
127+
<label className='text-xs text-card font-medium'>
128+
Select Token <span className='text-red-500'>*</span>
129+
</label>
130+
<Select value={token} onValueChange={setToken} disabled={isLoading}>
131+
<SelectTrigger className='w-full !h-12 flex items-center !gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border focus:ring-0'>
132+
<SelectValue placeholder='Select' />
133+
</SelectTrigger>
134+
<SelectContent className='max-h-[200px] bg-background rounded-[12px] font-normal text-base text-placeholder border border-stepper-border overflow-y-auto'>
135+
<SelectItem value='XLM'>XLM</SelectItem>
136+
<SelectItem value='USDT'>USDT</SelectItem>
137+
<SelectItem value='USDC'>USDC</SelectItem>
138+
</SelectContent>
139+
</Select>
140+
</div>
141+
142+
<div className='flex flex-col gap-1'>
143+
<label className='text-xs text-card font-medium'>
144+
Network <span className='text-red-500'>*</span>
145+
</label>
146+
<div className='w-full h-12 flex items-center gap-3 p-4 rounded-[12px] bg-stepper-foreground border border-stepper-border'>
147+
<input
148+
value={network}
149+
onChange={e => setNetwork(e.target.value)}
150+
type='text'
151+
className='w-full bg-transparent font-normal text-base text-placeholder focus:outline-none'
152+
disabled={isLoading}
153+
/>
154+
</div>
155+
</div>
156+
157+
<div className='flex flex-col gap-1'>
158+
<label className='text-xs text-card font-medium'>
159+
Wallet Address <span className='text-red-500'>*</span>
160+
</label>
161+
<BoundlessButton
162+
type='button'
163+
onClick={handleCopyAddress}
164+
className='w-full h-12 flex items-center justify-center gap-3 p-4 rounded-[12px] bg-blue-600 hover:bg-blue-700 text-white transition-colors'
165+
disabled={isLoading}
166+
>
167+
<Check className='w-4 h-4' />
168+
<span className='font-normal text-base'>{walletAddress}</span>
169+
<Copy className='w-4 h-4' />
170+
</BoundlessButton>
171+
</div>
172+
173+
<div className='flex items-center gap-2 mt-2'>
174+
<Checkbox
175+
id='anonymous'
176+
checked={keepAnonymous}
177+
onCheckedChange={checked => setKeepAnonymous(checked as boolean)}
178+
disabled={isLoading}
179+
className='border-stepper-border data-[state=checked]:bg-primary data-[state=checked]:border-primary'
180+
/>
181+
<label htmlFor='anonymous' className='text-card text-sm font-normal'>
182+
Keep me anonymous
183+
</label>
184+
</div>
185+
186+
<BoundlessButton
187+
type='button'
188+
disabled={!isFormValid || isLoading}
189+
onClick={handleSubmit}
190+
className={`w-full mt-4 h-12 text-base font-medium transition-colors ${
191+
isFormValid && !isLoading
192+
? 'bg-primary text-background border border-primary hover:bg-primary/90'
193+
: 'bg-stepper-foreground text-card/30 border border-stepper-border cursor-not-allowed'
194+
}`}
195+
>
196+
Confirm Contribution
197+
</BoundlessButton>
198+
</div>
199+
</div>
200+
);
201+
}

0 commit comments

Comments
 (0)