Skip to content

Commit 1364b46

Browse files
authored
Feature/implement dynamic campaign actions and sumary views (#134)
* feat: add CampaignTable component and integrate into UserPage - Introduced CampaignTable component to display campaign details with filtering options. - Updated UserPage to include CampaignTable, enhancing the user interface with campaign information. - Commented out RecentContributions and GrantHistory components for future implementation. * feat: add CampaignSummary component for detailed campaign insights - Introduced CampaignSummary component to display campaign details, milestones, and backing history. - Integrated the component into CampaignTable for improved user experience. - Enhanced UI with responsive design and interactive elements for milestone expansion. - Updated BoundlessSheet styling for better scrollbar visibility.
1 parent 57e2df7 commit 1364b46

4 files changed

Lines changed: 1170 additions & 5 deletions

File tree

app/user/page.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { PriceDisplay } from '@/components/PriceDisplay';
33
import Card from '@/components/card';
44
import RecentProjects from '@/components/overview/RecentProjects';
55
import { mockProjects } from '@/lib/mock';
6-
import RecentContributions from '@/components/overview/ReecntContributions';
7-
import GrantHistory from '@/components/overview/GrantHistory';
6+
// import RecentContributions from '@/components/overview/ReecntContributions';
7+
// import GrantHistory from '@/components/overview/GrantHistory';
88
import PageTransition from '@/components/PageTransition';
99
import { Coins, History } from 'lucide-react';
1010
import { useAuth } from '@/hooks/use-auth';
11+
import CampaignTable from '@/components/campaigns/CampaignTable';
1112
import { useEffect, useState } from 'react';
1213

1314
import LoadingSpinner from '@/components/LoadingSpinner';
@@ -93,12 +94,15 @@ export default function UserPage() {
9394
<div className='space-y-8'>
9495
{/* Recent Projects - Full Width */}
9596
<RecentProjects projects={mockProjects} />
97+
<div className='bg-[#1C1C1C] p-4 sm:p-6 rounded-[12px] flex flex-col gap-6 sm:gap-8 w-full'>
98+
<CampaignTable />
99+
</div>
96100

97101
{/* Recent Contributions and Grant History - Side by Side on larger screens */}
98-
<div className='grid grid-cols-1 xl:grid-cols-2 gap-6'>
102+
{/* <div className='grid grid-cols-1 xl:grid-cols-2 gap-6'>
99103
<RecentContributions projects={[]} />
100104
<GrantHistory projects={[]} />
101-
</div>
105+
</div> */}
102106
</div>
103107
</div>
104108
</div>
Lines changed: 362 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,362 @@
1+
import React, { useState } from 'react';
2+
import BoundlessSheet from '../sheet/boundless-sheet';
3+
import Image from 'next/image';
4+
import { Badge } from '../ui/badge';
5+
import { Avatar, AvatarFallback, AvatarImage } from '../ui/avatar';
6+
import Link from 'next/link';
7+
import { Progress } from '../ui/progress';
8+
import { BoundlessButton } from '../buttons';
9+
import {
10+
CalendarIcon,
11+
Clock,
12+
ChevronDown,
13+
ChevronRight,
14+
MessageCircleMore,
15+
ThumbsUp,
16+
Users,
17+
Wallet,
18+
Check,
19+
} from 'lucide-react';
20+
import { ScrollArea } from '../ui/scroll-area';
21+
import { formatDate } from '@/lib/utils';
22+
import { Button } from '../ui/button';
23+
24+
const CampaignSummary = ({
25+
open,
26+
setOpen,
27+
}: {
28+
open: boolean;
29+
setOpen: (open: boolean) => void;
30+
}) => {
31+
const [expandedMilestone, setExpandedMilestone] = useState<string | null>(
32+
null
33+
);
34+
35+
// Mock milestone data
36+
const milestones = [
37+
{
38+
id: '1',
39+
title: 'Project Planning & Research',
40+
description:
41+
'Complete market research, competitor analysis, and detailed project planning with timeline and resource allocation.',
42+
deliveryDate: new Date('2024-03-15'),
43+
fundPercentage: 20,
44+
},
45+
{
46+
id: '2',
47+
title: 'MVP Development',
48+
description:
49+
'Develop and test the minimum viable product with core features and basic functionality.',
50+
deliveryDate: new Date('2024-05-20'),
51+
fundPercentage: 40,
52+
},
53+
{
54+
id: '3',
55+
title: 'Beta Testing & Refinement',
56+
description:
57+
'Conduct comprehensive beta testing, gather user feedback, and implement necessary improvements.',
58+
deliveryDate: new Date('2024-07-10'),
59+
fundPercentage: 25,
60+
},
61+
{
62+
id: '4',
63+
title: 'Launch & Marketing',
64+
description:
65+
'Official product launch with marketing campaign and user acquisition strategy.',
66+
deliveryDate: new Date('2024-08-30'),
67+
fundPercentage: 15,
68+
},
69+
];
70+
71+
const toggle = (id: string) => {
72+
setExpandedMilestone(expandedMilestone === id ? null : id);
73+
};
74+
75+
const calculateFundAmount = (percentage: number) => {
76+
return (123000 * percentage) / 100;
77+
};
78+
79+
// Mock backing history data
80+
const backingHistory = [
81+
{
82+
id: '1',
83+
name: 'Collins Odumeje',
84+
wallet: 'GDS3...GB7',
85+
amount: 2300,
86+
time: '3s',
87+
avatar: 'https://github.com/shadcn.png',
88+
isVerified: true,
89+
},
90+
{
91+
id: '2',
92+
name: 'Collins Odumeje',
93+
wallet: 'GDS3...GB7',
94+
amount: 2300,
95+
time: '19d',
96+
avatar: 'https://github.com/shadcn.png',
97+
isVerified: true,
98+
},
99+
{
100+
id: '3',
101+
name: 'Collins Odumeje',
102+
wallet: 'GDS3...GB7',
103+
amount: 2300,
104+
time: '2w',
105+
avatar: 'https://github.com/shadcn.png',
106+
isVerified: true,
107+
},
108+
{
109+
id: '4',
110+
name: 'Anonymous',
111+
wallet: 'GDS3...GB7',
112+
amount: 2300,
113+
time: 'Aug 05, 2025',
114+
avatar: null,
115+
isVerified: false,
116+
},
117+
{
118+
id: '5',
119+
name: 'Collins Odumeje',
120+
wallet: 'GDS3...GB7',
121+
amount: 2300,
122+
time: 'Aug 05, 2025',
123+
avatar: 'https://github.com/shadcn.png',
124+
isVerified: true,
125+
},
126+
];
127+
return (
128+
<BoundlessSheet
129+
open={open}
130+
setOpen={setOpen}
131+
title='Campaign Summary'
132+
side='bottom'
133+
>
134+
<div className='max-w-[500px] mx-auto space-y-7'>
135+
<div className='space-y-4'>
136+
<div className='relative w-full rounded-[12px] overflow-hidden'>
137+
<Image
138+
src='/banner.png'
139+
alt='Campaign Summary'
140+
width={500}
141+
height={500}
142+
className='object-cover w-full h-full'
143+
/>
144+
</div>
145+
<div className='space-y-4'>
146+
<div>
147+
<div className='flex items-center gap-3'>
148+
<h2 className='text-white text-2xl font-medium'>Boundless</h2>
149+
<Badge className='rounded-none'>Successful</Badge>
150+
</div>
151+
<div className='flex items-center gap-2 text-[#B5B5B5]'>
152+
<span>#web3</span>
153+
<span>#crowdfunding</span>
154+
</div>
155+
</div>
156+
<div className='flex items-center gap-2'>
157+
<Avatar>
158+
<AvatarImage src='https://github.com/shadcn.png' />
159+
<AvatarFallback>CN</AvatarFallback>
160+
</Avatar>
161+
<div>
162+
<h2 className='text-white text-lg font-medium'>
163+
Collins Odumeje
164+
</h2>
165+
</div>
166+
</div>
167+
<p className='text-white'>
168+
This campaign successfully reached its funding goal. Contributions
169+
are now being distributed through escrow as milestones are
170+
completed.{' '}
171+
<Link href='/campaigns/123' className='text-primary underline'>
172+
Click to track milestone progress
173+
</Link>
174+
</p>
175+
</div>
176+
<div className='space-y-2'>
177+
<div className='flex justify-between'>
178+
<div className='flex flex-col'>
179+
<p className='text-[#B5B5B5] text-xs font-medium'>Raised</p>
180+
<p className='text-[#F5F5F5] text-sm font-medium'>
181+
$123,000.00
182+
</p>
183+
</div>
184+
<div className='flex flex-col'>
185+
<p className='text-[#B5B5B5] text-xs font-medium'>Target</p>
186+
<p className='text-[#F5F5F5] text-sm font-medium'>
187+
$123,000.00
188+
</p>
189+
</div>
190+
</div>
191+
<Progress
192+
value={50}
193+
className='h-2 bg-[#1671D9]/20 [&>div]:bg-[#1671D9]'
194+
/>
195+
<div className='flex justify-between items-center'>
196+
<div className='flex gap-3'>
197+
<BoundlessButton
198+
variant='outline'
199+
size='sm'
200+
className='bg-[#212121]'
201+
>
202+
<ThumbsUp className='w-4 h-4 text-white fill-white' />
203+
<span className='text-white'>100k</span>
204+
</BoundlessButton>
205+
<BoundlessButton
206+
variant='outline'
207+
size='sm'
208+
className='bg-[#212121]'
209+
>
210+
<MessageCircleMore className='w-4 h-4 text-white' />
211+
<span className='text-white'>100k</span>
212+
</BoundlessButton>
213+
</div>
214+
<div className='w-px h-6 bg-white/20'></div>
215+
<div className='flex items-center gap-2'>
216+
<Users className='w-4 h-4 text-white' />
217+
<span className='text-white'>100 backers</span>
218+
</div>
219+
<div className='w-px h-6 bg-white/20'></div>
220+
<div className='flex items-center gap-2'>
221+
<Clock className='w-4 h-4 text-white' />
222+
<span className='text-white'>100 days left</span>
223+
</div>
224+
</div>
225+
</div>
226+
</div>
227+
<div className='space-y-4'>
228+
<div>
229+
<h2 className='text-white text-lg font-semibold'>
230+
Campaign Details
231+
</h2>
232+
<ScrollArea className='h-[159px] pr-4 mt-2' type='always'>
233+
<p className='text-[#B5B5B5]'>
234+
Boundless is a trustless, decentralized application (dApp) that
235+
empowers changemakers and builders to raise funds transparently
236+
without intermediaries. Campaigns are structured around clearly
237+
defined milestones, with funds held in escrow and released only
238+
upon approval. Grant creators can launch programs with
239+
rule-based logic, and applicants can apply with proposals that
240+
go through public validation. The platform is built on the
241+
Stellar blockchain and powered by Soroban smart contracts to
242+
ensure transparency, security, and autonomy.
243+
</p>
244+
</ScrollArea>
245+
</div>
246+
<div>
247+
<h2 className='text-white text-lg font-semibold'>Milestones</h2>
248+
<div className='space-y-3'>
249+
{milestones.map((milestone, idx) => {
250+
const isExpanded = expandedMilestone === milestone.id;
251+
return (
252+
<div key={milestone.id} className='space-y-2'>
253+
<div className='text-xs text-white'>
254+
Milestone {idx + 1}
255+
</div>
256+
<div className='rounded-[12px] bg-[#1C1C1C] border border-[#2B2B2B]'>
257+
<div
258+
className='flex items-center justify-between p-3 pb-3 cursor-pointer select-none'
259+
onClick={() => toggle(milestone.id)}
260+
>
261+
<div className='text-white text-base font-semibold'>
262+
{milestone.title || `Milestone ${idx + 1}`}
263+
</div>
264+
<button
265+
type='button'
266+
className='text-gray-400 hover:text-white'
267+
aria-label={isExpanded ? 'Collapse' : 'Expand'}
268+
>
269+
<ChevronDown
270+
className={`h-4 w-4 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
271+
/>
272+
</button>
273+
</div>
274+
{isExpanded && (
275+
<div className='p-5 pt-0 space-y-3'>
276+
<div className='text-[#fff]'>
277+
{milestone.description}
278+
</div>
279+
<div className='flex gap-6 text-gray-300 text-sm'>
280+
<div className='flex items-center gap-2'>
281+
<CalendarIcon className='h-4 w-4' />
282+
<span className='text-white'>
283+
{formatDate(milestone.deliveryDate)}
284+
</span>
285+
</div>
286+
<div className='flex items-center gap-2'>
287+
<span className='text-[#B5B5B5]'>
288+
$
289+
{calculateFundAmount(
290+
milestone.fundPercentage
291+
).toLocaleString()}{' '}
292+
({milestone.fundPercentage || 0}%)
293+
</span>
294+
</div>
295+
</div>
296+
</div>
297+
)}
298+
</div>
299+
</div>
300+
);
301+
})}
302+
</div>
303+
</div>
304+
<div>
305+
<div className='flex justify-between items-center'>
306+
<h2 className='text-white text-lg font-semibold'>
307+
Backing History
308+
</h2>
309+
<Button variant='ghost' className='text-white underline'>
310+
View all <ChevronRight className='w-4 h-4' />
311+
</Button>
312+
</div>
313+
<div className='mt-4 space-y-3'>
314+
{backingHistory.map(backer => (
315+
<div
316+
key={backer.id}
317+
className='flex items-center py-2 border-t border-[#2B2B2B] gap-16'
318+
>
319+
<div className='flex items-center gap-3 flex-1'>
320+
<div className='relative'>
321+
<Avatar className='w-10 h-10'>
322+
<AvatarImage src={backer.avatar || undefined} />
323+
<AvatarFallback className='bg-blue-500 text-white'>
324+
{backer.name.charAt(0)}
325+
</AvatarFallback>
326+
</Avatar>
327+
{backer.isVerified && (
328+
<div className='absolute -bottom-1 -right-1 bg-gray-600 rounded-full p-0.5'>
329+
<Check className='w-3 h-3 text-white' />
330+
</div>
331+
)}
332+
</div>
333+
<div>
334+
<div className='text-white font-medium'>
335+
{backer.name}
336+
</div>
337+
<div className='flex items-center gap-1 text-[#B5B5B5] text-sm'>
338+
<Wallet className='w-3 h-3' />
339+
<span>{backer.wallet}</span>
340+
</div>
341+
</div>
342+
</div>
343+
<div className='text-center w-24'>
344+
<div className='text-[#B5B5B5] font-medium'>
345+
${backer.amount.toLocaleString()}
346+
</div>
347+
</div>
348+
<div className='text-right w-20'>
349+
<div className='text-[#B5B5B5] text-sm'>{backer.time}</div>
350+
</div>
351+
</div>
352+
))}
353+
</div>
354+
</div>
355+
<div></div>
356+
</div>
357+
</div>
358+
</BoundlessSheet>
359+
);
360+
};
361+
362+
export default CampaignSummary;

0 commit comments

Comments
 (0)