Skip to content

Commit 3ac4a18

Browse files
authored
feat: Implement wallet protection across various components (#148)
- Introduced hook to manage wallet connection requirements. - Added component for prompting users to connect their wallets. - Updated , , , , and other components to utilize wallet protection. - Enhanced user experience by ensuring wallet connection is required for critical actions like launching campaigns and submitting milestones. - Added loading skeletons for user and project pages to improve UI responsiveness during data fetching.
1 parent 5eed105 commit 3ac4a18

23 files changed

Lines changed: 799 additions & 297 deletions

app/test/page.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,24 @@ import LaunchCampaignFlow from '@/components/project/LaunchCampaignFlow';
55
import BoundlessSheet from '@/components/sheet/boundless-sheet';
66
import { Button } from '@/components/ui/button';
77
import { Rocket } from 'lucide-react';
8+
import { useWalletProtection } from '@/hooks/use-wallet-protection';
9+
import WalletRequiredModal from '@/components/wallet/WalletRequiredModal';
810

911
export default function TestPage() {
1012
const [showLaunchFlow, setShowLaunchFlow] = useState(false);
1113

14+
// Wallet protection hook
15+
const {
16+
requireWallet,
17+
showWalletModal,
18+
handleWalletConnected,
19+
closeWalletModal,
20+
} = useWalletProtection({
21+
actionName: 'test launch campaign',
22+
});
23+
1224
const handleOpenModal = () => {
13-
setShowLaunchFlow(true);
25+
requireWallet(() => setShowLaunchFlow(true));
1426
};
1527

1628
const handleCloseModal = () => {
@@ -55,6 +67,14 @@ export default function TestPage() {
5567
onComplete={handleCloseModal}
5668
/>
5769
</BoundlessSheet>
70+
71+
{/* Wallet Required Modal */}
72+
<WalletRequiredModal
73+
open={showWalletModal}
74+
onOpenChange={closeWalletModal}
75+
actionName='test launch campaign'
76+
onWalletConnected={handleWalletConnected}
77+
/>
5878
</div>
5979
</div>
6080
);

app/user/backing-history/page.tsx

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

app/user/page.tsx

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import { Coins, History } from 'lucide-react';
77
import { useAuth } from '@/hooks/use-auth';
88
import CampaignTable from '@/components/campaigns/CampaignTable';
99
import { useEffect, useState } from 'react';
10-
11-
import LoadingSpinner from '@/components/LoadingSpinner';
10+
import { UserPageSkeleton } from '@/components/skeleton/UserPageSkeleton';
1211

1312
export default function UserPage() {
1413
const { user, isLoading } = useAuth();
@@ -20,11 +19,7 @@ export default function UserPage() {
2019

2120
// Show loading until client-side hydration is complete
2221
if (!mounted || isLoading) {
23-
return (
24-
<div className='min-h-screen flex items-center justify-center'>
25-
<LoadingSpinner size='lg' />
26-
</div>
27-
);
22+
return <UserPageSkeleton />;
2823
}
2924

3025
return (

app/user/projects/page.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,30 @@
11
'use client';
22
import PageTransition from '@/components/PageTransition';
33
import Projects from '@/components/Projects';
4-
import React from 'react';
4+
import React, { useEffect, useState } from 'react';
5+
import { ProjectsPageSkeleton } from '@/components/skeleton/ProjectsSkeleton';
6+
7+
const Page = () => {
8+
const [mounted, setMounted] = useState(false);
9+
10+
useEffect(() => {
11+
setMounted(true);
12+
}, []);
13+
14+
// Show loading until client-side hydration is complete
15+
if (!mounted) {
16+
return <ProjectsPageSkeleton />;
17+
}
518

6-
const page = () => {
719
return (
820
<PageTransition>
921
<div className='min-h-screen'>
1022
<div className='p-4 sm:p-6 lg:p-8 max-w-7xl mx-auto'>
11-
{/* <div className='bg-[#1C1C1C] p-4 sm:p-6 mx-6 rounded-[12px] flex flex-col gap-6 sm:gap-8 '> */}
12-
<Projects />
13-
{/* </div> */}
23+
<Projects />
1424
</div>
1525
</div>
16-
</PageTransition >
26+
</PageTransition>
1727
);
1828
};
1929

20-
export default page;
30+
export default Page;

components/Projects.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
'use client';
22
import React, { useState, useEffect, useCallback } from 'react';
33
import { RecentProjectsProps } from '@/types/project';
4-
import { Plus, ChevronDown, Loader2 } from 'lucide-react';
4+
import { Plus, ChevronDown } from 'lucide-react';
55
import ProjectCard from './project-card';
66
import EmptyState from './EmptyState';
77
import { BoundlessButton } from './buttons';
@@ -20,6 +20,7 @@ import { getProjects } from '@/lib/api/project';
2020
import { toast } from 'sonner';
2121
import { useAuth } from '@/hooks/use-auth';
2222
import { useProjectSheetStore } from '@/lib/stores/project-sheet-store';
23+
import { ProjectsSkeleton } from './skeleton/ProjectsSkeleton';
2324

2425
type StatusFilter =
2526
| 'all'
@@ -93,6 +94,19 @@ const Projects = () => {
9394
fetchProjects();
9495
}, [fetchProjects]);
9596

97+
if (loading) {
98+
return (
99+
<motion.div
100+
className='bg-[#1C1C1C] p-4 sm:p-6 rounded-[12px] flex flex-col gap-6 sm:gap-8 w-full'
101+
initial='hidden'
102+
animate='visible'
103+
variants={fadeInUp}
104+
>
105+
<ProjectsSkeleton />
106+
</motion.div>
107+
);
108+
}
109+
96110
return (
97111
<motion.div
98112
className='bg-[#1C1C1C] p-4 sm:p-6 rounded-[12px] flex flex-col gap-6 sm:gap-8 w-full'
@@ -108,7 +122,6 @@ const Projects = () => {
108122
<h2 className='text-white text-base sm:text-lg xl:text-xl font-semibold leading-[120%] tracking-[-0.4px]'>
109123
{tabFilter === 'mine' ? 'My Projects' : 'All Projects'}
110124
</h2>
111-
112125
</div>
113126
<div className='flex flex-col sm:flex-row items-start sm:items-center gap-2 sm:gap-3 w-full xl:w-auto'>
114127
<Tabs
@@ -180,16 +193,7 @@ const Projects = () => {
180193
className='grid grid-cols-1 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols- xl:grid-cols-3 gap-4 sm:gap-6'
181194
variants={staggerContainer}
182195
>
183-
{loading ? (
184-
<motion.div className='col-span-full' variants={fadeInUp}>
185-
<div className='flex items-center justify-center py-12'>
186-
<div className='flex items-center gap-3'>
187-
<Loader2 className='w-6 h-6 animate-spin text-[#1671D9]' />
188-
<span className='text-[#B5B5B5]'>Loading projects...</span>
189-
</div>
190-
</div>
191-
</motion.div>
192-
) : error ? (
196+
{error ? (
193197
<motion.div className='col-span-full' variants={fadeInUp}>
194198
<div className='flex items-center justify-center py-12'>
195199
<div className='text-center'>
@@ -268,7 +272,6 @@ const Projects = () => {
268272
iconPosition='right'
269273
onClick={() => {
270274
sheet.openInitialize();
271-
272275
}}
273276
>
274277
New Project.

components/auth/LoginForm.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ const LoginForm = () => {
9292
'Login successful but session not found. Please try again.',
9393
});
9494
}
95+
router.push(callbackUrl);
9596
} else {
9697
form.setError('root', {
9798
type: 'manual',

components/campaigns/CampaignTable.tsx

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
11
import React, { useState, useEffect, useCallback } from 'react';
22
import { Button } from '../ui/button';
3-
import {
4-
MoreVerticalIcon,
5-
CheckIcon,
6-
ChevronDownIcon,
7-
Loader2Icon,
8-
} from 'lucide-react';
3+
import { MoreVerticalIcon, CheckIcon, ChevronDown } from 'lucide-react';
94
import { Tabs, TabsList, TabsTrigger } from '../ui/tabs';
105
import {
116
DropdownMenu,
@@ -27,6 +22,7 @@ import {
2722
} from '@/lib/data/campaigns-mock';
2823
import BackingHistory from './backing-history';
2924
import { sampleBackers } from '@/lib/data/backing-history-mock';
25+
import { CampaignTableSkeleton } from '../skeleton/UserPageSkeleton';
3026

3127
const CampaignRow = ({
3228
campaign,
@@ -518,6 +514,14 @@ const CampaignTable = () => {
518514
window.scrollTo({ top: 0, behavior: 'smooth' });
519515
};
520516

517+
if (loading) {
518+
return (
519+
<div className='space-y-6 min-h-full'>
520+
<CampaignTableSkeleton />
521+
</div>
522+
);
523+
}
524+
521525
return (
522526
<div className='space-y-6 min-h-full'>
523527
<div className='flex flex-col xl:flex-row justify-between items-start xl:items-center gap-3 sm:gap-4 xl:gap-0'>
@@ -566,7 +570,7 @@ const CampaignTable = () => {
566570
filterOptions.find(option => option.value === statusFilter)
567571
?.label
568572
}{' '}
569-
<ChevronDownIcon className='w-3 h-3 sm:w-4 sm:h-4' />
573+
<ChevronDown className='w-3 h-3 sm:w-4 sm:h-4' />
570574
</BoundlessButton>
571575
</DropdownMenuTrigger>
572576
<DropdownMenuContent
@@ -602,14 +606,7 @@ const CampaignTable = () => {
602606
</div>
603607

604608
<div className='space-y-3'>
605-
{loading ? (
606-
<div className='flex items-center justify-center py-12'>
607-
<div className='flex items-center gap-3'>
608-
<Loader2Icon className='w-6 h-6 animate-spin text-[#1671D9]' />
609-
<span className='text-[#B5B5B5]'>Loading campaigns...</span>
610-
</div>
611-
</div>
612-
) : error ? (
609+
{error ? (
613610
<div className='flex items-center justify-center py-12'>
614611
<div className='text-center'>
615612
<p className='text-red-400 mb-2'>{error}</p>

components/campaigns/LaunchCampaignFlow.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ import {
2424
} from '@/components/ui/select';
2525
import { BoundlessButton } from '../buttons';
2626
import { Checkbox } from '../ui/checkbox';
27+
import { useWalletProtection } from '@/hooks/use-wallet-protection';
28+
import WalletRequiredModal from '@/components/wallet/WalletRequiredModal';
2729

2830
interface LaunchCampaignFlowProps {
2931
onComplete: () => void;
@@ -60,6 +62,16 @@ const LaunchCampaignFlow: React.FC<LaunchCampaignFlowProps> = ({
6062
const [currentPhase, setCurrentPhase] = useState<'details' | 'escrow'>(
6163
'details'
6264
);
65+
66+
// Wallet protection hook
67+
const {
68+
requireWallet,
69+
showWalletModal,
70+
handleWalletConnected,
71+
closeWalletModal,
72+
} = useWalletProtection({
73+
actionName: 'launch campaign',
74+
});
6375
const [formData, setFormData] = useState({
6476
title: 'Boundless',
6577
description:
@@ -280,9 +292,17 @@ const LaunchCampaignFlow: React.FC<LaunchCampaignFlowProps> = ({
280292
escrowTerms={escrowTerms}
281293
isEscrowValid={isEscrowValid}
282294
onBack={handleBackPhase}
283-
onComplete={onComplete}
295+
onComplete={() => requireWallet(onComplete)}
284296
/>
285297
)}
298+
299+
{/* Wallet Required Modal */}
300+
<WalletRequiredModal
301+
open={showWalletModal}
302+
onOpenChange={closeWalletModal}
303+
actionName='launch campaign'
304+
onWalletConnected={handleWalletConnected}
305+
/>
286306
</div>
287307
);
288308
};

0 commit comments

Comments
 (0)