Skip to content

Commit 785270b

Browse files
committed
make contracts 'saveable'
1 parent cd6f43b commit 785270b

2 files changed

Lines changed: 192 additions & 99 deletions

File tree

app/pages/AddressDetailPage.jsx

Lines changed: 109 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,19 @@ import Skeleton from "../components/Skeleton";
66
import Tabs from "../components/Tabs";
77
import ContractInteraction from "../components/ContractInteraction";
88
import DecodedTransactionInput from "../components/DecodedTransactionInput";
9-
import { fetchAddressByAddress, fetchAddressTransactions } from "../lib/BlockchainApi";
9+
import { fetchAddressByAddress, fetchAddressTransactions, fetchERC20Transfers, fetchInternalTransactions } from "../lib/BlockchainApi";
1010
import { parseABI } from "../lib/AbiDecoder";
11-
import { Copy, Edit2, FileCode, Tag, Wallet, X } from "lucide-react";
11+
import { ArrowDownLeft, ArrowUpRight, Bookmark, BookmarkCheck, Copy, Edit2, FileCode, GitBranch, Tag, Trash2, Wallet, X } from "lucide-react";
1212

1313
export default function AddressDetailPage({ address }) {
1414
const [addressData, setAddressData] = useState(null);
1515
const [transactions, setTransactions] = useState([]);
16+
const [erc20Transfers, setErc20Transfers] = useState([]);
17+
const [internalTxs, setInternalTxs] = useState([]);
1618
const [isLoadingAddress, setIsLoadingAddress] = useState(true);
1719
const [isLoadingTxs, setIsLoadingTxs] = useState(true);
20+
const [isLoadingErc20, setIsLoadingErc20] = useState(true);
21+
const [isLoadingInternal, setIsLoadingInternal] = useState(true);
1822
const {
1923
rpcUrl,
2024
getContractABI,
@@ -23,6 +27,9 @@ export default function AddressDetailPage({ address }) {
2327
getAddressLabel,
2428
saveAddressLabel,
2529
removeAddressLabel,
30+
isAddressSaved,
31+
saveAddress,
32+
unsaveAddress,
2633
} = useBlockchain();
2734
const { navigate } = useRouter();
2835
const [copiedAddress, setCopiedAddress] = useState(false);
@@ -36,6 +43,8 @@ export default function AddressDetailPage({ address }) {
3643
const [showLabelInput, setShowLabelInput] = useState(false);
3744
const [labelInput, setLabelInput] = useState("");
3845

46+
const isSaved = isAddressSaved(address);
47+
3948
useEffect(() => {
4049
const savedABI = getContractABI(address);
4150
if (savedABI) {
@@ -77,6 +86,38 @@ export default function AddressDetailPage({ address }) {
7786
loadTransactions();
7887
}, [rpcUrl, address]);
7988

89+
// TODO: needed/used?
90+
useEffect(() => {
91+
const loadErc20Transfers = async () => {
92+
if (!rpcUrl || !address) return;
93+
try {
94+
const data = await fetchERC20Transfers(rpcUrl, address);
95+
setErc20Transfers(data);
96+
} catch (error) {
97+
console.error("Error fetching ERC20 transfers:", error);
98+
} finally {
99+
setIsLoadingErc20(false);
100+
}
101+
};
102+
loadErc20Transfers();
103+
}, [rpcUrl, address]);
104+
105+
// TODO: needed/used?
106+
useEffect(() => {
107+
const loadInternalTxs = async () => {
108+
if (!rpcUrl || !address) return;
109+
try {
110+
const data = await fetchInternalTransactions(rpcUrl, address);
111+
setInternalTxs(data);
112+
} catch (error) {
113+
console.error("Error fetching internal transactions:", error);
114+
} finally {
115+
setIsLoadingInternal(false);
116+
}
117+
};
118+
loadInternalTxs();
119+
}, [rpcUrl, address]);
120+
80121
const copyToClipboard = (text, field = null) => {
81122
navigator.clipboard.writeText(text);
82123
if (field) {
@@ -88,6 +129,14 @@ export default function AddressDetailPage({ address }) {
88129
}
89130
};
90131

132+
const handleToggleSave = () => {
133+
if (isSaved) {
134+
unsaveAddress(address);
135+
} else {
136+
saveAddress(address);
137+
}
138+
};
139+
91140
const handleParseABI = () => {
92141
try {
93142
setAbiError("");
@@ -374,20 +423,10 @@ export default function AddressDetailPage({ address }) {
374423
}`}
375424
>
376425
{addressData.isContract
377-
? (
378-
<>
379-
<FileCode className="w-5 h-5" />
380-
Contract
381-
</>
382-
)
383-
: (
384-
<>
385-
<Wallet className="w-5 h-5" />
386-
Wallet
387-
</>
388-
)}
389-
</span>
390-
)}
426+
? (<> <FileCode className="w-5 h-5" /> Contract </>)
427+
: (<> <Wallet className="w-5 h-5" /> Wallet </>)}
428+
</span>
429+
)}
391430
</div>
392431

393432
<div className="float-right">
@@ -397,64 +436,75 @@ export default function AddressDetailPage({ address }) {
397436
<Tag className="w-4 h-4 text-[oklch(0.65_0.25_151)]" />
398437
<span className="text-foreground font-medium">{label}</span>
399438
</div>
400-
<button
401-
onClick={handleEditLabel}
402-
className="p-2 text-muted-foreground hover:text-foreground transition-colors"
403-
title="Edit label"
404-
>
405-
<Edit2 className="w-4 h-4" />
406-
</button>
407-
<button
408-
onClick={handleRemoveLabel}
439+
<button
440+
onClick={handleEditLabel}
441+
className="p-2 text-muted-foreground hover:text-foreground transition-colors"
442+
title="Edit label"
443+
>
444+
<Edit2 className="w-4 h-4" />
445+
</button>
446+
<button
447+
onClick={handleRemoveLabel}
409448
className="p-2 text-muted-foreground hover:text-red-500 transition-colors"
410449
title="Remove label"
411-
>
450+
>
412451
<X className="w-4 h-4" />
413-
</button>
414-
</div>
415-
)}
452+
</button>
453+
<button
454+
onClick={handleToggleSave}
455+
className={`btn ${
456+
isSaved ? "btn-primary" : "btn-outline"
457+
} flex items-center gap-2`}
458+
>
459+
{isSaved
460+
? (<> <BookmarkCheck className="w-4 h-4" /> Saved </>)
461+
: (<> <Bookmark className="w-4 h-4" /> Save </> )
462+
}
463+
</button>
464+
</div>
465+
)}
416466

417-
{showLabelInput && (
467+
{showLabelInput && (
418468
<div className="mb-6 flex items-center gap-2">
419-
<input
420-
type="text"
421-
value={labelInput}
422-
onChange={(e) => setLabelInput(e.target.value)}
469+
<input
470+
type="text"
471+
value={labelInput}
472+
onChange={(e) => setLabelInput(e.target.value)}
423473
placeholder="Enter a label or note for this address..."
424474
className="input flex-1 max-w-md"
425-
onKeyDown={(e) => e.key === "Enter" && handleSaveLabel()}
426-
autoFocus
427-
/>
475+
onKeyDown={(e) => e.key === "Enter" && handleSaveLabel()}
476+
autoFocus
477+
/>
428478
<button onClick={handleSaveLabel} className="btn btn-primary">
429-
Save
430-
</button>
431-
<button
432-
onClick={() => {
433-
setShowLabelInput(false);
434-
setLabelInput("");
435-
}}
479+
Save
480+
</button>
481+
<button
482+
onClick={() => {
483+
setShowLabelInput(false);
484+
setLabelInput("");
485+
}}
436486
className="btn btn-outline"
437-
>
438-
Cancel
439-
</button>
440-
</div>
441-
)}
487+
>
488+
Cancel
489+
</button>
490+
</div>
491+
)}
442492

443-
{!label && !showLabelInput && (
493+
{!label && !showLabelInput && (
444494
<div className="mb-6">
445-
<button
446-
onClick={() => setShowLabelInput(true)}
495+
<button
496+
onClick={() => setShowLabelInput(true)}
447497
className="flex items-center gap-2 text-muted-foreground hover:text-[oklch(0.65_0.25_151)] transition-colors text-sm"
448-
>
498+
>
449499
<Tag className="w-4 h-4" />
450-
Add label
451-
</button>
452-
</div>
453-
)}
454-
</div>
500+
Add label
501+
</button>
502+
</div>
503+
)}
504+
</div>
455505

456506
<Tabs tabs={tabs} defaultTab="overview" />
457-
</div>
507+
</div>
458508
</main>
459509
<Footer />
460510
</div>

app/pages/AddressesPage.jsx

Lines changed: 83 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
1-
2-
31
import { useState } from "react"
4-
import { useRouter } from "../App"
2+
import { useRouter, useBlockchain } from "../App"
53
import NavBar from "../components/NavBar"
64
import Footer from "../components/Footer"
7-
import { Search, Wallet, FileCode } from "lucide-react"
5+
import { Wallet, FileCode, Bookmark, Tag, Trash2 } from "lucide-react"
86

97
export default function AddressesPage() {
108
const [searchQuery, setSearchQuery] = useState("")
119
const { navigate } = useRouter()
10+
const { savedAddresses, unsaveAddress, getAddressLabel } = useBlockchain()
1211

1312
const handleSearch = () => {
1413
if (searchQuery.trim()) {
@@ -48,51 +47,95 @@ export default function AddressesPage() {
4847
</form>
4948

5049
</div>
51-
<div className="space-y-4">
52-
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
53-
<div className="card border-primary/10 hover:border-primary/30 transition-colors">
54-
<div className="card-header">
55-
<div className="flex items-center gap-3">
56-
<div className="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
57-
<Wallet className="w-5 h-5 text-primary" />
58-
</div>
59-
<div>
60-
<h3 className="card-title text-lg">Wallet Addresses</h3>
61-
<p className="card-description text-sm">View balance and transaction history</p>
50+
<div className="space-y-4">
51+
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
52+
<div className="card border-primary/10 hover:border-primary/30 transition-colors">
53+
<div className="card-header">
54+
<div className="flex items-center gap-3">
55+
<div className="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
56+
<Wallet className="w-5 h-5 text-primary" />
57+
</div>
58+
<div>
59+
<h3 className="card-title text-lg">Wallet Addresses</h3>
60+
<p className="card-description text-sm">View balance and transaction history</p>
61+
</div>
6262
</div>
6363
</div>
64+
<div className="card-content">
65+
<ul className="space-y-2 text-sm text-muted-foreground">
66+
<li>• Check ETH balance</li>
67+
<li>• View transaction history</li>
68+
<li>• See sent and received transactions</li>
69+
</ul>
70+
</div>
6471
</div>
65-
<div className="card-content">
66-
<ul className="space-y-2 text-sm text-muted-foreground">
67-
<li>• Check ETH balance</li>
68-
<li>• View transaction history</li>
69-
<li>• See sent and received transactions</li>
70-
</ul>
71-
</div>
72-
</div>
7372

74-
<div className="card border-primary/10 hover:border-primary/30 transition-colors">
75-
<div className="card-header">
76-
<div className="flex items-center gap-3">
77-
<div className="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
78-
<FileCode className="w-5 h-5 text-primary" />
79-
</div>
80-
<div>
81-
<h3 className="card-title text-lg">Smart Contracts</h3>
82-
<p className="card-description text-sm">Explore contract details and code</p>
73+
<div className="card border-primary/10 hover:border-primary/30 transition-colors">
74+
<div className="card-header">
75+
<div className="flex items-center gap-3">
76+
<div className="w-10 h-10 bg-primary/10 rounded-lg flex items-center justify-center">
77+
<FileCode className="w-5 h-5 text-primary" />
78+
</div>
79+
<div>
80+
<h3 className="card-title text-lg">Smart Contracts</h3>
81+
<p className="card-description text-sm">Explore contract details and code</p>
82+
</div>
8383
</div>
8484
</div>
85-
</div>
86-
<div className="card-content">
87-
<ul className="space-y-2 text-sm text-muted-foreground">
88-
<li>View contract code</li>
89-
<li>Check contract balance</li>
90-
<li>• See contract interactions</li>
91-
</ul>
85+
<div className="card-content">
86+
<ul className="space-y-2 text-sm text-muted-foreground">
87+
<li>• View contract code</li>
88+
<li>Check contract balance</li>
89+
<li>See contract interactions</li>
90+
</ul>
91+
</div>
9292
</div>
9393
</div>
9494
</div>
95-
</div>
95+
96+
{savedAddresses && savedAddresses.length > 0 && (
97+
<div className="max-w-3xl mx-auto">
98+
<div className="flex items-center gap-2 mb-4">
99+
<Bookmark className="w-5 h-5 text-[oklch(0.65_0.25_151)]" />
100+
<h2 className="text-xl font-semibold">Saved Addresses</h2>
101+
<span className="badge badge-secondary">{savedAddresses.length}</span>
102+
</div>
103+
<div className="space-y-3">
104+
{savedAddresses.map((addr) => {
105+
const label = getAddressLabel(addr)
106+
return (
107+
<div key={addr} className="card hover:border-[oklch(0.65_0.25_151)]/30 transition-colors">
108+
<div className="card-content py-4">
109+
<div className="flex items-center justify-between">
110+
<div className="flex flex-col gap-1">
111+
<button
112+
onClick={() => navigate("address-detail", { address: addr })}
113+
className="font-mono text-sm text-[oklch(0.65_0.25_151)] hover:underline text-left"
114+
>
115+
{addr}
116+
</button>
117+
{label && (
118+
<span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
119+
<Tag className="w-3 h-3" />
120+
{label}
121+
</span>
122+
)}
123+
</div>
124+
<button
125+
onClick={() => unsaveAddress(addr)}
126+
className="p-2 text-muted-foreground hover:text-destructive rounded-lg hover:bg-destructive/10 transition-colors"
127+
title="Remove from saved"
128+
>
129+
<Trash2 className="w-4 h-4" />
130+
</button>
131+
</div>
132+
</div>
133+
</div>
134+
)
135+
})}
136+
</div>
137+
</div>
138+
)}
96139
</main>
97140

98141
<Footer />

0 commit comments

Comments
 (0)