Skip to content

Commit 44e5e91

Browse files
committed
feat: init transactions page
1 parent 33a255e commit 44e5e91

File tree

13 files changed

+1033
-601
lines changed

13 files changed

+1033
-601
lines changed

app/queue/page.tsx

Whitespace-only changes.

app/transactions/new/page.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use client';
2+
3+
import { PageContainer } from '@/components/Layout/PageContainer';
4+
import { Card, CardContent } from '@/components/ui/card';
5+
import { Coins } from 'lucide-react';
6+
import { useSelectedSafeAddress } from '@/providers/SafeProvider';
7+
import { TransactionProcess } from '@/components/Transactions/process/TransactionProcess';
8+
9+
export default function NewTransactionPage() {
10+
const { selectedSafeAddress } = useSelectedSafeAddress();
11+
12+
if (!selectedSafeAddress) {
13+
return (
14+
<PageContainer>
15+
<Card className='max-w-md mx-auto'>
16+
<CardContent className='flex items-center justify-center py-12'>
17+
<div className='text-center space-y-4'>
18+
<Coins className='h-12 w-12 text-muted-foreground mx-auto' />
19+
<div>
20+
<h3 className='text-lg font-medium mb-2'>No Safe Selected</h3>
21+
<p className='text-muted-foreground'>Please select a Safe to create transactions</p>
22+
</div>
23+
</div>
24+
</CardContent>
25+
</Card>
26+
</PageContainer>
27+
);
28+
}
29+
30+
return (
31+
<PageContainer>
32+
<TransactionProcess />
33+
</PageContainer>
34+
);
35+
}

components/Breadcrumb/AppBreadcrumb.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const routeConfig: Record<string, { label: string; clickable?: boolean }> = {
1919
'/wallets': { label: 'Wallets' },
2020
'/wallets/create': { label: 'Create Wallet' },
2121
'/transactions': { label: 'Transactions', clickable: false },
22+
'/transactions/new': { label: 'New Transaction' },
2223
'/transactions/queue': { label: 'Queue' },
2324
'/transactions/history': { label: 'History' },
2425
'/settings': { label: 'Settings' },
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
'use client';
2+
3+
import { Button } from '../ui/button';
4+
import { QrCode, Copy, ExternalLink, Settings, Check } from 'lucide-react';
5+
import { useState } from 'react';
6+
import { useSelectedSafeAddress } from '@/providers/SafeProvider';
7+
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
8+
9+
export function ActionButtons() {
10+
const { selectedSafeAddress } = useSelectedSafeAddress();
11+
const [copied, setCopied] = useState(false);
12+
13+
const handleCopy = async () => {
14+
if (!selectedSafeAddress) return;
15+
16+
try {
17+
await navigator.clipboard.writeText(selectedSafeAddress);
18+
setCopied(true);
19+
setTimeout(() => setCopied(false), 2000);
20+
} catch (err) {
21+
console.error('Failed to copy address:', err);
22+
}
23+
};
24+
25+
const handleOpenInExplorer = () => {
26+
if (!selectedSafeAddress) return;
27+
28+
const explorerUrl = `https://etherscan.io/address/${selectedSafeAddress}`;
29+
window.open(explorerUrl, '_blank');
30+
};
31+
32+
const handleShowQRCode = () => {
33+
// TODO: Implement QR code modal or popup
34+
console.log('Show QR code for:', selectedSafeAddress);
35+
};
36+
37+
const handleOpenSettings = () => {
38+
// TODO: Navigate to settings or open settings modal
39+
console.log('Open settings');
40+
};
41+
42+
if (!selectedSafeAddress) {
43+
return null;
44+
}
45+
46+
return (
47+
<div className='flex items-center gap-2'>
48+
<Tooltip>
49+
<TooltipTrigger asChild>
50+
<Button variant='secondary' size='icon' className='size-8' onClick={handleShowQRCode}>
51+
<QrCode />
52+
</Button>
53+
</TooltipTrigger>
54+
<TooltipContent>
55+
<p>Show QR Code</p>
56+
</TooltipContent>
57+
</Tooltip>
58+
59+
<Tooltip>
60+
<TooltipTrigger asChild>
61+
<Button variant='secondary' size='icon' className='size-8' onClick={handleCopy}>
62+
<Copy />
63+
</Button>
64+
</TooltipTrigger>
65+
<TooltipContent>
66+
<p>{copied ? 'Copied!' : 'Copy address'}</p>
67+
</TooltipContent>
68+
</Tooltip>
69+
70+
<Tooltip>
71+
<TooltipTrigger asChild>
72+
<Button variant='secondary' size='icon' className='size-8' onClick={handleOpenInExplorer}>
73+
<ExternalLink />
74+
</Button>
75+
</TooltipTrigger>
76+
<TooltipContent>
77+
<p>View on Etherscan</p>
78+
</TooltipContent>
79+
</Tooltip>
80+
81+
<Tooltip>
82+
<TooltipTrigger asChild>
83+
<Button variant='secondary' size='icon' className='size-8' onClick={handleOpenSettings}>
84+
<Settings />
85+
</Button>
86+
</TooltipTrigger>
87+
<TooltipContent>
88+
<p>Settings</p>
89+
</TooltipContent>
90+
</Tooltip>
91+
</div>
92+
);
93+
}

components/Sidebar/AppSiderbarContent.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import {
1818
History,
1919
ListChecks,
2020
ListTodo,
21+
ListTodoIcon,
22+
Plus,
2123
Search,
2224
Settings,
2325
Wallet,
@@ -43,10 +45,15 @@ const baseItems = [
4345
];
4446

4547
const transactionItems = [
48+
{
49+
title: 'New Transaction',
50+
url: '/transactions/new',
51+
icon: Plus,
52+
},
4653
{
4754
title: 'Queue',
4855
url: '/transactions/queue',
49-
icon: ListChecks,
56+
icon: ListTodoIcon,
5057
},
5158
{
5259
title: 'History',

components/Sidebar/SafeWalletSelect.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import {
1313
DropdownMenuSeparator,
1414
} from '@/components/ui/dropdown-menu';
1515
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
16-
import { Button } from '@/components/ui/button';
1716
import { useSelectedSafeAddress, useOwnerSafeAddress, useCurrentChain } from '@/providers/SafeProvider';
1817
import { useWallet } from '@/hooks/useWallet';
1918
import { getNetworkIcon } from '@/components/BlockChainNetworkIcon';
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
'use client';
2+
3+
import { useState } from 'react';
4+
import { Button } from '@/components/ui/button';
5+
import { Input } from '@/components/ui/input';
6+
import { Label } from '@/components/ui/label';
7+
import { Textarea } from '@/components/ui/textarea';
8+
import { TransactionData } from './TransactionProcess';
9+
10+
interface CustomTransactionBuilderProps {
11+
onComplete: (data: Partial<TransactionData>) => void;
12+
}
13+
14+
export function CustomTransactionBuilder({ onComplete }: CustomTransactionBuilderProps) {
15+
const [formData, setFormData] = useState({
16+
to: '',
17+
value: '',
18+
data: '',
19+
gasLimit: '',
20+
maxFeePerGas: '',
21+
maxPriorityFeePerGas: '',
22+
});
23+
24+
const handleSubmit = (e: React.FormEvent) => {
25+
e.preventDefault();
26+
27+
if (!formData.to) {
28+
alert('Please enter the contract address');
29+
return;
30+
}
31+
32+
onComplete({
33+
to: formData.to,
34+
value: formData.value || '0',
35+
data: formData.data,
36+
gasLimit: formData.gasLimit,
37+
maxFeePerGas: formData.maxFeePerGas,
38+
maxPriorityFeePerGas: formData.maxPriorityFeePerGas,
39+
});
40+
};
41+
42+
const handleInputChange = (field: string, value: string) => {
43+
setFormData((prev) => ({ ...prev, [field]: value }));
44+
};
45+
46+
return (
47+
<form onSubmit={handleSubmit} className='space-y-6'>
48+
{/* Contract Address */}
49+
<div className='space-y-2'>
50+
<Label htmlFor='to'>Contract Address *</Label>
51+
<Input
52+
id='to'
53+
type='text'
54+
placeholder='0x...'
55+
value={formData.to}
56+
onChange={(e) => handleInputChange('to', e.target.value)}
57+
required
58+
/>
59+
<p className='text-sm text-muted-foreground'>Enter the smart contract address to interact with</p>
60+
</div>
61+
62+
{/* Value (Optional) */}
63+
<div className='space-y-2'>
64+
<Label htmlFor='value'>Value (ETH)</Label>
65+
<Input
66+
id='value'
67+
type='number'
68+
step='any'
69+
placeholder='0.0'
70+
value={formData.value}
71+
onChange={(e) => handleInputChange('value', e.target.value)}
72+
/>
73+
<p className='text-sm text-muted-foreground'>Amount of ETH to send with the transaction (optional)</p>
74+
</div>
75+
76+
{/* Transaction Data */}
77+
<div className='space-y-2'>
78+
<Label htmlFor='data'>Transaction Data</Label>
79+
<Textarea
80+
id='data'
81+
placeholder='0x...'
82+
value={formData.data}
83+
onChange={(e) => handleInputChange('data', e.target.value)}
84+
rows={4}
85+
/>
86+
<p className='text-sm text-muted-foreground'>Hex-encoded function call data (optional)</p>
87+
</div>
88+
89+
{/* Gas Settings */}
90+
<div className='grid gap-4 md:grid-cols-3'>
91+
<div className='space-y-2'>
92+
<Label htmlFor='gasLimit'>Gas Limit</Label>
93+
<Input
94+
id='gasLimit'
95+
type='number'
96+
placeholder='21000'
97+
value={formData.gasLimit}
98+
onChange={(e) => handleInputChange('gasLimit', e.target.value)}
99+
/>
100+
</div>
101+
<div className='space-y-2'>
102+
<Label htmlFor='maxFeePerGas'>Max Fee Per Gas (Gwei)</Label>
103+
<Input
104+
id='maxFeePerGas'
105+
type='number'
106+
placeholder='20'
107+
value={formData.maxFeePerGas}
108+
onChange={(e) => handleInputChange('maxFeePerGas', e.target.value)}
109+
/>
110+
</div>
111+
<div className='space-y-2'>
112+
<Label htmlFor='maxPriorityFeePerGas'>Max Priority Fee (Gwei)</Label>
113+
<Input
114+
id='maxPriorityFeePerGas'
115+
type='number'
116+
placeholder='2'
117+
value={formData.maxPriorityFeePerGas}
118+
onChange={(e) => handleInputChange('maxPriorityFeePerGas', e.target.value)}
119+
/>
120+
</div>
121+
</div>
122+
123+
{/* Submit Button */}
124+
<div className='flex justify-end'>
125+
<Button type='submit' className='w-full md:w-auto'>
126+
Continue to Preview
127+
</Button>
128+
</div>
129+
</form>
130+
);
131+
}

0 commit comments

Comments
 (0)