Skip to content

Commit 33a255e

Browse files
committed
feat: add queue page
1 parent 8a3f41a commit 33a255e

File tree

7 files changed

+503
-83
lines changed

7 files changed

+503
-83
lines changed

app/transactions/queue/page.tsx

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,42 @@
1+
'use client';
2+
3+
import { PageContainer } from '@/components/Layout/PageContainer';
4+
import { TransactionsQueue } from '@/components/Transactions/TransactionsQueue';
5+
import { useSelectedSafeAddress } from '@/providers/SafeProvider';
6+
import { Card, CardContent } from '@/components/ui/card';
7+
import { Clock } from 'lucide-react';
8+
19
export default function TransactionsQueuePage() {
2-
return <div>TransactionsQueuePage</div>;
10+
const { selectedSafeAddress } = useSelectedSafeAddress();
11+
12+
if (!selectedSafeAddress) {
13+
return (
14+
<PageContainer>
15+
<Card>
16+
<CardContent className='flex items-center justify-center py-12'>
17+
<div className='text-center'>
18+
<Clock className='h-12 w-12 text-muted-foreground mx-auto mb-4' />
19+
<h3 className='text-lg font-medium mb-2'>No Safe Selected</h3>
20+
<p className='text-muted-foreground'>Please select a Safe to view its pending transactions</p>
21+
</div>
22+
</CardContent>
23+
</Card>
24+
</PageContainer>
25+
);
26+
}
27+
28+
return (
29+
<PageContainer>
30+
<div className='space-y-6'>
31+
<div>
32+
<h1 className='text-2xl font-bold tracking-tight'>Transaction Queue</h1>
33+
<p className='text-muted-foreground'>
34+
Review and manage pending transactions that require confirmation or execution.
35+
</p>
36+
</div>
37+
38+
<TransactionsQueue />
39+
</div>
40+
</PageContainer>
41+
);
342
}

components/BlockChainNetworkIcon.tsx

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,38 +17,40 @@ export function getNetworkIcon({
1717
networkId,
1818
variant,
1919
size,
20+
color,
2021
}: {
2122
networkId: string;
2223
variant: 'branded' | 'mono' | 'background';
2324
size: number;
25+
color?: string;
2426
}) {
2527
switch (networkId) {
2628
case 'ethereum':
27-
return <NetworkEthereum variant={variant} size={size} />;
29+
return <NetworkEthereum variant={variant} size={size} color={color} />;
2830
case 'polygon':
29-
return <NetworkPolygon variant={variant} size={size} />;
31+
return <NetworkPolygon variant={variant} size={size} color={color} />;
3032
case 'arbitrum':
31-
return <NetworkArbitrumOne variant={variant} size={size} />;
33+
return <NetworkArbitrumOne variant={variant} size={size} color={color} />;
3234
case 'optimism':
33-
return <NetworkOptimism variant={variant} size={size} />;
35+
return <NetworkOptimism variant={variant} size={size} color={color} />;
3436
case 'base':
35-
return <NetworkBase variant={variant} size={size} />;
37+
return <NetworkBase variant={variant} size={size} color={color} />;
3638
case 'bsc':
37-
return <NetworkBinanceSmartChain variant={variant} size={size} />;
39+
return <NetworkBinanceSmartChain variant={variant} size={size} color={color} />;
3840
case 'avalanche':
39-
return <NetworkAvalanche variant={variant} size={size} />;
41+
return <NetworkAvalanche variant={variant} size={size} color={color} />;
4042
case 'gnosis':
41-
return <NetworkGnosis variant={variant} size={size} />;
43+
return <NetworkGnosis variant={variant} size={size} color={color} />;
4244
case 'hemi':
43-
return <NetworkHemi variant={variant} size={size} />;
45+
return <NetworkHemi variant={variant} size={size} color={color} />;
4446
case 'linea':
45-
return <NetworkLinea variant={variant} size={size} />;
47+
return <NetworkLinea variant={variant} size={size} color={color} />;
4648
case 'berachain':
47-
return <NetworkBerachain variant={variant} size={size} />;
49+
return <NetworkBerachain variant={variant} size={size} color={color} />;
4850
case 'polygon-zkevm':
49-
return <NetworkPolygonZkevm variant={variant} size={size} />;
51+
return <NetworkPolygonZkevm variant={variant} size={size} color={color} />;
5052
case 'BaseSepolia':
51-
return <NetworkBase variant={variant} size={size} />;
53+
return <NetworkBase variant={variant} size={size} color={color} />;
5254
default:
5355
return null;
5456
}

components/Sidebar/AppSiderbarContent.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {
2323
Wallet,
2424
} from 'lucide-react';
2525
import { usePathname, useRouter } from 'next/navigation';
26-
import SafeWalletGroup from './SafeWalletGroup';
2726

2827
const baseItems = [
2928
{

components/Sidebar/AppSiderbarHeader.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { SafeWalletSelect } from './SafeWalletSelect';
55

66
export function AppSidebarHeader() {
77
return (
8-
<SidebarHeader className='h-12'>
8+
<SidebarHeader>
99
<SafeWalletSelect />
1010
</SidebarHeader>
1111
);
Lines changed: 137 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
11
'use client';
22

3-
import React from 'react';
4-
import { ChevronDown, Plus, Wallet, Shield } from 'lucide-react';
5-
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
6-
import { Button } from '@/components/ui/button';
7-
import { cn } from '@/lib/utils';
3+
import * as React from 'react';
4+
import { Check, ChevronsUpDown, Shield, Wallet, Plus, Smile } from 'lucide-react';
85
import { useRouter } from 'next/navigation';
9-
import { useSelectedSafeAddress, useOwnerSafeAddress } from '@/providers/SafeProvider';
6+
import { cn } from '@/lib/utils';
7+
8+
import {
9+
DropdownMenu,
10+
DropdownMenuContent,
11+
DropdownMenuItem,
12+
DropdownMenuTrigger,
13+
DropdownMenuSeparator,
14+
} from '@/components/ui/dropdown-menu';
15+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
16+
import { Button } from '@/components/ui/button';
17+
import { useSelectedSafeAddress, useOwnerSafeAddress, useCurrentChain } from '@/providers/SafeProvider';
1018
import { useWallet } from '@/hooks/useWallet';
19+
import { getNetworkIcon } from '@/components/BlockChainNetworkIcon';
1120

1221
export function SafeWalletSelect() {
1322
const { selectedSafeAddress, setSelectedSafeAddress } = useSelectedSafeAddress();
1423
const ownerSafeAddress = useOwnerSafeAddress();
15-
const { account: eoaAddress, isConnected } = useWallet();
24+
const { chainName, chainId } = useCurrentChain();
25+
const { account, isConnected } = useWallet();
1626
const router = useRouter();
1727

18-
const handleSafeSelect = async (safeAddress: string) => {
28+
const handleSafeSelect = (safeAddress: string) => {
1929
setSelectedSafeAddress(safeAddress);
2030
};
2131

2232
const handleCreateSafe = () => {
2333
router.push('/wallets/create');
2434
};
2535

26-
const handleConnectWallet = () => {
27-
// TODO: Implement wallet connection logic
28-
console.log('Connect wallet clicked');
29-
};
30-
3136
const formatAddress = (address: string) => {
3237
return `${address.slice(0, 6)}...${address.slice(-4)}`;
3338
};
@@ -54,74 +59,140 @@ export function SafeWalletSelect() {
5459
return address.slice(2, 4).toUpperCase();
5560
};
5661

57-
// EOA not connected state
62+
const getNetworkId = (chainId: number | null): string => {
63+
if (!chainId) return '';
64+
65+
const networkMap: { [key: number]: string } = {
66+
1: 'ethereum',
67+
10: 'optimism',
68+
56: 'bsc',
69+
100: 'gnosis',
70+
137: 'polygon',
71+
324: 'polygon-zkevm',
72+
8453: 'base',
73+
42161: 'arbitrum',
74+
43114: 'avalanche',
75+
84532: 'BaseSepolia',
76+
11155111: 'ethereum', // Sepolia uses ethereum icon
77+
};
78+
79+
return networkMap[chainId] || '';
80+
};
81+
82+
// Hardware wallet not connected state
5883
if (!isConnected) {
5984
return (
60-
<div className='h-14 px-4 flex items-center gap-2'>
61-
<Wallet className='w-4 h-4 text-muted-foreground' />
62-
<span className='text-sm text-muted-foreground'>Connect Wallet to Continue</span>
63-
</div>
85+
<SidebarMenu>
86+
<SidebarMenuItem>
87+
<SidebarMenuButton size='lg' disabled>
88+
<div className='bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg'>
89+
<Smile className='size-4' />
90+
</div>
91+
<div className='flex flex-col gap-0.5 leading-none'>
92+
<span className='font-medium text-sidebar-muted-foreground'>Hello</span>
93+
<span className='text-xs text-sidebar-muted-foreground/70'>Nice to meet you</span>
94+
</div>
95+
</SidebarMenuButton>
96+
</SidebarMenuItem>
97+
</SidebarMenu>
6498
);
6599
}
66100

67101
// EOA connected but no Safe wallets
68102
if (ownerSafeAddress.length === 0) {
69103
return (
70-
<div className='h-14 px-4 flex items-center gap-3'>
71-
<div className='flex items-center gap-2'>
72-
<Shield className='w-4 h-4 text-muted-foreground' />
73-
<span className='text-sm text-muted-foreground'>No Safe Wallets</span>
74-
</div>
75-
<Button variant='outline' size='sm' onClick={handleCreateSafe} className='h-8 px-3'>
76-
<Plus className='w-3 h-3 mr-1' />
77-
Create
78-
</Button>
79-
</div>
104+
<SidebarMenu>
105+
<SidebarMenuItem>
106+
<SidebarMenuButton size='lg' onClick={handleCreateSafe}>
107+
<div className='bg-sidebar-primary text-sidebar-primary-foreground flex aspect-square size-8 items-center justify-center rounded-lg'>
108+
<Shield className='size-4' />
109+
</div>
110+
<div className='flex flex-col gap-0.5 leading-none'>
111+
<span className='font-medium'>No Safe Wallets</span>
112+
<span className='text-xs text-sidebar-muted-foreground'>Create your first Safe</span>
113+
</div>
114+
<Plus className='ml-auto size-4' />
115+
</SidebarMenuButton>
116+
</SidebarMenuItem>
117+
</SidebarMenu>
80118
);
81119
}
82120

83121
// EOA connected and has Safe wallets
122+
const currentSafe = selectedSafeAddress || ownerSafeAddress[0];
123+
84124
return (
85-
<Select value={selectedSafeAddress || undefined} onValueChange={handleSafeSelect}>
86-
<SelectTrigger className='h-14 px-4 w-auto min-w-[200px] border-0 bg-transparent hover:bg-accent transition-colors'>
87-
<div className='flex items-center gap-3'>
88-
<div
89-
className={cn(
90-
'w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium text-white',
91-
getAddressColor(selectedSafeAddress || ownerSafeAddress[0])
92-
)}
93-
>
94-
{getAddressAvatar(selectedSafeAddress || ownerSafeAddress[0])}
95-
</div>
96-
<div className='flex flex-col items-start'>
97-
<span className='text-sm font-medium'>{formatAddress(selectedSafeAddress || ownerSafeAddress[0])}</span>
98-
<span className='text-xs text-muted-foreground'>Safe Wallet</span>
99-
</div>
100-
</div>
101-
</SelectTrigger>
102-
<SelectContent>
103-
{ownerSafeAddress.map((safeAddr) => (
104-
<SelectItem key={safeAddr} value={safeAddr}>
105-
<div className='flex items-center gap-3'>
106-
<div
107-
className={cn(
108-
'w-5 h-5 rounded-full flex items-center justify-center text-xs font-medium text-white',
109-
getAddressColor(safeAddr)
125+
<SidebarMenu>
126+
<SidebarMenuItem>
127+
<DropdownMenu>
128+
<DropdownMenuTrigger asChild>
129+
<SidebarMenuButton
130+
size='lg'
131+
className='data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground'
132+
>
133+
<div className='flex items-center justify-center '>
134+
{getNetworkIcon({
135+
networkId: getNetworkId(chainId),
136+
variant: 'mono',
137+
size: 26,
138+
}) || (
139+
<div
140+
className={cn(
141+
'flex aspect-square size-8 items-center justify-center rounded-lg text-white text-xs font-medium',
142+
getAddressColor(currentSafe)
143+
)}
144+
>
145+
{getAddressAvatar(currentSafe)}
146+
</div>
110147
)}
148+
</div>
149+
<div className='flex flex-col gap-0.5 leading-none'>
150+
<span className='font-medium'>{formatAddress(currentSafe)}</span>
151+
<span className='text-xs text-sidebar-muted-foreground'>{chainName || 'Safe Wallet'}</span>
152+
</div>
153+
<ChevronsUpDown className='ml-auto size-4' />
154+
</SidebarMenuButton>
155+
</DropdownMenuTrigger>
156+
<DropdownMenuContent className='w-[280px]' align='start' side='right'>
157+
{ownerSafeAddress.map((safeAddr) => (
158+
<DropdownMenuItem
159+
key={safeAddr}
160+
onClick={() => handleSafeSelect(safeAddr)}
161+
className='flex items-center gap-3 py-3'
111162
>
112-
{getAddressAvatar(safeAddr)}
163+
<div className='flex items-center justify-center'>
164+
{getNetworkIcon({
165+
networkId: getNetworkId(chainId),
166+
variant: 'branded',
167+
size: 30,
168+
}) || (
169+
<div
170+
className={cn(
171+
'flex aspect-square size-6 items-center justify-center rounded-full text-white text-xs font-medium',
172+
getAddressColor(safeAddr)
173+
)}
174+
>
175+
{getAddressAvatar(safeAddr)}
176+
</div>
177+
)}
178+
</div>
179+
<div className='flex flex-col gap-0.5'>
180+
<span className='font-medium'>{formatAddress(safeAddr)}</span>
181+
<span className='text-xs text-muted-foreground'>{chainName || 'Safe Wallet'}</span>
182+
</div>
183+
{safeAddr === selectedSafeAddress && <Check className='ml-auto size-4 text-primary' />}
184+
</DropdownMenuItem>
185+
))}
186+
<DropdownMenuSeparator />
187+
<DropdownMenuItem onClick={handleCreateSafe} className='py-3'>
188+
<div className='flex aspect-square size-6 items-center justify-center rounded-full bg-sidebar-muted'>
189+
<Plus className='size-3' />
113190
</div>
114-
<span>{formatAddress(safeAddr)}</span>
115-
</div>
116-
</SelectItem>
117-
))}
118-
<div className='border-t pt-2 mt-2'>
119-
<Button variant='ghost' size='sm' onClick={handleCreateSafe} className='w-full justify-start h-8'>
120-
<Plus className='w-3 h-3 mr-2' />
121-
Create New Safe
122-
</Button>
123-
</div>
124-
</SelectContent>
125-
</Select>
191+
<span className='ml-3'>Create New Safe</span>
192+
</DropdownMenuItem>
193+
</DropdownMenuContent>
194+
</DropdownMenu>
195+
</SidebarMenuItem>
196+
</SidebarMenu>
126197
);
127198
}

0 commit comments

Comments
 (0)