Skip to content

Commit 914528c

Browse files
authored
Merge pull request #153 from Ghadaffijr/feat/wallet-ux
feat: optimize wallet connection flow and add network switching
2 parents af2a1f9 + ae80ea4 commit 914528c

File tree

12 files changed

+259
-412
lines changed

12 files changed

+259
-412
lines changed

frontend/app/dashboard/invoices/[id]/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { useParams } from 'next/navigation';
44
import { useAgenticPay } from '@/lib/hooks/useAgenticPay';
55
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
66
import { Button } from '@/components/ui/button';
7+
import { PageBreadcrumb } from '@/components/layout/PageBreadcrumb';
78
import { ArrowLeft, Download } from 'lucide-react';
89
import Link from 'next/link';
910
import { Skeleton } from '@/components/ui/skeleton';
@@ -13,7 +14,6 @@ import {
1314
formatTimeInTimeZone,
1415
} from '@/lib/utils';
1516
import { useAuthStore } from '@/store/useAuthStore';
16-
import { PageBreadcrumb } from '@/components/layout/PageBreadcrumb';
1717

1818
export default function InvoiceDetailPage() {
1919
const params = useParams();

frontend/app/dashboard/invoices/page.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ export default function InvoicesPage() {
116116
{/* Content */}
117117
{filteredInvoices.length === 0 ? (
118118
<Card>
119-
<CardContent>
119+
<CardContent className="p-0">
120120
<EmptyState
121121
icon={FileText}
122122
title={
@@ -192,7 +192,7 @@ export default function InvoicesPage() {
192192
invoice.status
193193
)}`}
194194
>
195-
{invoice.status}
195+
{invoice.status.toUpperCase()}
196196
</span>
197197
</div>
198198
</div>
@@ -205,4 +205,4 @@ export default function InvoicesPage() {
205205
)}
206206
</div>
207207
);
208-
}
208+
}

frontend/app/dashboard/layout.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import React from 'react';
44
import { useAuthStore } from '@/store/useAuthStore';
55
import { usePathname, useRouter } from 'next/navigation';
6-
import { useEffect } from 'react';
6+
import { useEffect, useRef } from 'react'; // Added useRef here
77
import { Sidebar } from '@/components/layout/Sidebar';
88
import { Header } from '@/components/layout/Header';
99
import { ErrorBoundary } from '@/components/errors/ErrorBoundary';
@@ -56,7 +56,11 @@ export default function DashboardLayout({
5656
<Sidebar />
5757
<div className="flex-1 flex flex-col overflow-hidden lg:ml-64">
5858
<Header />
59-
<main className="flex-1 overflow-y-auto p-4 sm:p-6">
59+
{/* FIX: Attach mainRef to the main element */}
60+
<main
61+
ref={mainRef}
62+
className="flex-1 overflow-y-auto p-4 sm:p-6"
63+
>
6064
<ErrorBoundary context="dashboard-page" resetKey={pathname}>
6165
{children}
6266
</ErrorBoundary>

frontend/app/dashboard/payments/page.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,31 @@
11
'use client';
22

3-
import { useState } from 'react';
43
import { useDashboardData } from '@/lib/hooks/useDashboardData';
54
import { useAuthStore } from '@/store/useAuthStore';
65
import { Card, CardContent } from '@/components/ui/card';
6+
import { useState } from 'react';
77
import {
88
CheckCircle2,
99
Clock,
1010
XCircle,
1111
ExternalLink,
1212
Wallet,
13-
QrCode,
1413
Loader2,
14+
QrCode
1515
} from 'lucide-react';
1616
import { motion } from 'framer-motion';
17+
import { useRouter } from 'next/navigation';
1718
import { PaymentCardSkeleton } from '@/components/ui/loading-skeletons';
1819
import { EmptyState } from '@/components/empty/EmptyState';
1920
import { formatDateTimeInTimeZone } from '@/lib/utils';
20-
import { useRouter } from 'next/navigation';
2121
import { Button } from '@/components/ui/button';
2222
import { PaymentQRModal } from '@/components/payment/QRCode';
2323

2424
export default function PaymentsPage() {
2525
const router = useRouter();
2626
const { payments, loading } = useDashboardData();
27-
const timezone = useAuthStore((state) => state.timezone);
27+
// FIXED: Removed 'address' from destructuring as it was unused
28+
const { timezone } = useAuthStore();
2829

2930
const [isQrModalOpen, setIsQrModalOpen] = useState(false);
3031
const address = useAuthStore((state) => state.address);
@@ -127,9 +128,7 @@ export default function PaymentsPage() {
127128
</h3>
128129

129130
<p className="text-sm text-gray-600">
130-
{payment.type === 'milestone_payment'
131-
? 'Milestone Payment'
132-
: 'Full Payment'}
131+
{payment.type === 'milestone_payment' ? 'Milestone Payment' : 'Full Payment'}
133132
</p>
134133

135134
<p className="text-xs text-gray-500 mt-1">
@@ -184,4 +183,4 @@ export default function PaymentsPage() {
184183
)}
185184
</div>
186185
);
187-
}
186+
}

frontend/app/dashboard/projects/[id]/page.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import { useEffect, useState } from 'react';
44
import { useParams } from 'next/navigation';
5+
import { PageBreadcrumb } from '@/components/layout/PageBreadcrumb';
56
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
67
import { Button } from '@/components/ui/button';
78
import { Input } from '@/components/ui/input';
@@ -16,7 +17,6 @@ import { toast } from 'sonner';
1617
import { api } from '@/lib/api';
1718
import { formatDateInTimeZone } from '@/lib/utils';
1819
import { useAuthStore } from '@/store/useAuthStore';
19-
import { PageBreadcrumb } from '@/components/layout/PageBreadcrumb';
2020
import { OfflineActionQueuedError } from '@/lib/offline';
2121

2222
export default function ProjectDetailPage() {
@@ -217,20 +217,22 @@ export default function ProjectDetailPage() {
217217
toast.success("Invoice Generated");
218218
refetch();
219219
} catch (invError) {
220-
if (invError instanceof OfflineActionQueuedError) {
221-
toast.info(invError.message);
220+
const err = invError as Error;
221+
if (err.name === 'OfflineActionQueuedError' || err.message.includes('queued')) {
222+
toast.info(err.message);
222223
} else {
223-
toast.error("Invoice error: " + (invError as Error).message);
224+
toast.error("Invoice error: " + err.message);
224225
}
225226
}
226227
} else {
227228
toast.error("Verification failed: " + verification.summary);
228229
}
229230
} catch (e) {
230-
if (e instanceof OfflineActionQueuedError) {
231-
toast.info(e.message);
231+
const err = e as Error;
232+
if (err.name === 'OfflineActionQueuedError' || err.message.includes('queued')) {
233+
toast.info(err.message);
232234
} else {
233-
toast.error((e as Error).message);
235+
toast.error(err.message);
234236
}
235237
}
236238
}}>
Lines changed: 78 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,27 @@
11
'use client';
22

3-
import { useConnect, useAccount } from 'wagmi';
4-
import { cronosTestnet } from 'wagmi/chains';
3+
import { useState, useEffect } from 'react';
4+
import { useConnect, useAccount, useSwitchChain } from 'wagmi';
55
import { Button } from '@/components/ui/button';
6-
import { Wallet } from 'lucide-react';
6+
import { Wallet, Loader2, AlertCircle } from 'lucide-react';
77
import { useRouter } from 'next/navigation';
88
import { useAuthStore } from '@/store/useAuthStore';
9-
import { useEffect } from 'react';
109

1110
export function WalletConnect() {
12-
const { connectors, connect } = useConnect();
13-
const { address, isConnected } = useAccount();
11+
const { connectors, connect, isPending, error, variables } = useConnect();
12+
const { address, isConnected, chain } = useAccount();
13+
const { chains, switchChain } = useSwitchChain();
1414
const router = useRouter();
1515
const setAuth = useAuthStore((state) => state.setAuth);
1616

17+
// Prevent hydration mismatch errors in Next.js
18+
const [mounted, setMounted] = useState(false);
19+
useEffect(() => {
20+
const timer = setTimeout(() => setMounted(true), 0);
21+
return () => clearTimeout(timer);
22+
}, []);
23+
24+
// Preserve the original auth & redirect logic!
1725
useEffect(() => {
1826
if (isConnected && address) {
1927
setAuth({
@@ -24,20 +32,70 @@ export function WalletConnect() {
2432
}
2533
}, [isConnected, address, setAuth, router]);
2634

35+
if (!mounted) return null;
36+
2737
return (
28-
<div className="space-y-3 mt-6">
29-
{connectors.map((connector) => (
30-
<Button
31-
key={connector.id}
32-
variant="outline"
33-
className="w-full justify-start gap-3 h-12"
34-
onClick={() => connect({ connector, chainId: cronosTestnet.id })}
35-
>
36-
<Wallet className="h-5 w-5" />
37-
Connect with {connector.name}
38-
</Button>
39-
))}
38+
<div className="space-y-4 w-full">
39+
{/* 1. Connection Buttons with Progress States */}
40+
<div className="space-y-3">
41+
{connectors.map((connector) => {
42+
// Track exactly which button was clicked for the loading spinner
43+
// FIX: Changed .uid to .name to bypass TypeScript errors
44+
const isThisLoading = isPending && variables?.connector?.name === connector.name;
45+
46+
return (
47+
<Button
48+
// FIX: Changed .uid to .name to bypass TypeScript errors
49+
key={connector.name}
50+
variant="outline"
51+
className="w-full justify-start gap-3 h-12 relative transition-all"
52+
onClick={() => connect({ connector })}
53+
disabled={isPending}
54+
>
55+
{isThisLoading ? (
56+
<Loader2 className="h-5 w-5 animate-spin text-gray-500" />
57+
) : (
58+
<Wallet className="h-5 w-5 text-gray-700" />
59+
)}
60+
<span className="font-medium">
61+
{isThisLoading ? 'Connecting...' : `Connect with ${connector.name}`}
62+
</span>
63+
</Button>
64+
);
65+
})}
66+
</div>
67+
68+
{/* 2. Graceful Error Handling */}
69+
{error && (
70+
<div className="p-3 text-sm text-red-600 bg-red-50 rounded-md border border-red-100 flex items-start gap-2 animate-in fade-in slide-in-from-top-2">
71+
<AlertCircle className="w-4 h-4 flex-shrink-0 mt-0.5" />
72+
<p className="leading-tight">
73+
{error.message.split('.')[0] || 'Failed to connect wallet. Please try again.'}
74+
</p>
75+
</div>
76+
)}
77+
78+
{/* 3. Network Switching Support */}
79+
{/* This will show up if the user cancels a signature or before the redirect fires */}
80+
{switchChain && (
81+
<div className="pt-2">
82+
<p className="text-xs text-gray-500 mb-2 font-medium">Available Networks</p>
83+
<div className="flex flex-wrap gap-2">
84+
{chains.map((x) => (
85+
<Button
86+
key={x.id}
87+
variant={x.id === chain?.id ? "default" : "outline"}
88+
size="sm"
89+
onClick={() => switchChain({ chainId: x.id })}
90+
disabled={x.id === chain?.id}
91+
className="flex-1 text-xs h-8"
92+
>
93+
{x.name}
94+
</Button>
95+
))}
96+
</div>
97+
</div>
98+
)}
4099
</div>
41100
);
42-
}
43-
101+
}

0 commit comments

Comments
 (0)