forked from Txio-labs/txio
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathObjectsTab.tsx
More file actions
160 lines (150 loc) · 7.72 KB
/
Copy pathObjectsTab.tsx
File metadata and controls
160 lines (150 loc) · 7.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import React, { useState } from 'react';
import { Box, Search, RefreshCw, ChevronLeft, Coins } from 'lucide-react';
import { Network } from '../../../types';
import { getObject } from '../../../services/suiService';
import type { WalletChainFamily } from '../../../wallet';
interface ObjectsTabProps {
connectedAddress: string | null;
walletFamily: WalletChainFamily | null;
network: Network;
objects: any[];
loadingObjects: boolean;
onRefreshObjects: () => void;
}
export const ObjectsTab: React.FC<ObjectsTabProps> = ({
connectedAddress,
walletFamily,
network,
objects,
loadingObjects,
onRefreshObjects,
}) => {
const [selectedObject, setSelectedObject] = useState<any | null>(null);
const [objectSearch, setObjectSearch] = useState('');
const [searchLoading, setSearchLoading] = useState(false);
const [searchError, setSearchError] = useState<string | null>(null);
const handleObjectSearch = async () => {
if (!objectSearch) return;
setSearchLoading(true);
setSelectedObject(null);
setSearchError(null);
try {
const res = await getObject(network, objectSearch);
if (res.result && res.result.data) {
setSelectedObject(res.result.data);
} else {
// RPC responded but returned no object data (e.g. deleted/invalid ID)
const rpcMessage = res.result?.error?.code || res.result?.error;
setSearchError(rpcMessage ? String(rpcMessage) : 'Object not found');
}
} catch (err) {
setSelectedObject(null);
setSearchError(err instanceof Error ? err.message : 'Object not found');
} finally {
setSearchLoading(false);
}
};
if (!connectedAddress) {
return (
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center opacity-50">
<Box size={24} className="mb-2 text-slate-600"/>
<p className="text-xs text-slate-500">Connect a Sui wallet to inspect owned objects</p>
</div>
);
}
if (walletFamily !== 'sui') {
return (
<div className="flex-1 flex flex-col items-center justify-center p-8 text-center">
<div className="w-14 h-14 rounded-2xl border border-slate-200 dark:border-white/10 bg-slate-100 dark:bg-white/[0.03] flex items-center justify-center mb-4">
<Box size={22} className="text-slate-500" />
</div>
<h3 className="text-sm font-bold text-slate-900 dark:text-white mb-2">Sui object inspection only</h3>
<p className="text-xs leading-6 text-slate-500 max-w-[220px]">
The object explorer currently reads Sui ownership data. Switch to a Sui wallet to browse on-chain objects here.
</p>
</div>
);
}
if (selectedObject) {
return (
<div className="flex-1 flex flex-col animate-in slide-in-from-right-4 duration-200">
<div className="shrink-0 flex items-center gap-2 p-3 border-b border-slate-200 dark:border-white/10 bg-white/80 dark:bg-near-black/50">
<button onClick={() => setSelectedObject(null)} className="p-1 hover:bg-slate-100 dark:hover:bg-white/10 rounded text-slate-400 hover:text-slate-900 dark:hover:text-slate-900 dark:text-white transition-colors">
<ChevronLeft size={16} />
</button>
<span className="text-xs font-bold text-slate-900 dark:text-white">Object Details</span>
</div>
<div className="flex-1 overflow-auto custom-scrollbar p-0 bg-white dark:bg-[#18181b]">
<div className="p-4 border-b border-slate-200 dark:border-white/10">
<label className="text-[10px] uppercase font-black text-slate-600 tracking-widest block mb-1">Object ID</label>
<div className="text-xs font-mono text-electric-violet break-all select-all">{selectedObject.objectId || selectedObject.data?.objectId}</div>
</div>
<div className="p-4 border-b border-slate-200 dark:border-white/10">
<label className="text-[10px] uppercase font-black text-slate-600 tracking-widest block mb-1">Type</label>
<div className="text-xs font-mono text-amber-500 break-all select-all">{selectedObject.type || selectedObject.data?.type}</div>
</div>
<div className="p-4">
<label className="text-[10px] uppercase font-black text-slate-600 tracking-widest block mb-2">Raw Content</label>
<pre className="text-[10px] font-mono text-slate-400 overflow-x-auto bg-slate-50 dark:bg-near-black p-3 rounded-lg border border-slate-200 dark:border-white/10">
{JSON.stringify(selectedObject, null, 2)}
</pre>
</div>
</div>
</div>
);
}
return (
<>
<div className="p-3 border-b border-slate-200 dark:border-white/10 flex gap-2">
<div className="relative flex-1">
<Search size={12} className="absolute left-2.5 top-1/2 -translate-y-1/2 text-slate-500" />
<input
className="w-full bg-slate-50 dark:bg-near-black border border-slate-200 dark:border-white/10 rounded-lg pl-8 pr-3 py-1.5 text-xs text-slate-900 dark:text-white focus:border-electric-violet outline-none transition-all placeholder:text-slate-600"
placeholder="Search Object ID..."
value={objectSearch}
onChange={(e) => { setObjectSearch(e.target.value); setSearchError(null); }}
onKeyDown={(e) => e.key === 'Enter' && handleObjectSearch()}
/>
</div>
<button onClick={onRefreshObjects} className="p-1.5 bg-slate-50 dark:bg-near-black border border-slate-200 dark:border-white/10 rounded-lg text-slate-400 hover:text-slate-900 dark:hover:text-slate-900 dark:text-white hover:border-slate-300 dark:border-white/20 transition-colors">
<RefreshCw size={14} className={loadingObjects ? 'animate-spin' : ''} />
</button>
</div>
{searchError && (
// Inline feedback for a failed/empty object lookup, instead of failing silently
<div className="px-3 pt-2 -mb-1 text-[11px] text-red-500 dark:text-red-400">
{searchError}
</div>
)}
<div className="flex-1 overflow-y-auto custom-scrollbar p-2 space-y-1">
{objects.map((obj, i) => {
const type = obj.data?.type || '';
const isCoin = type.includes('::coin::Coin');
const shortType = type.split('::').pop().split('<')[0];
return (
<button key={i} onClick={() => setSelectedObject(obj)} className="group flex items-center gap-3 p-2.5 rounded-lg hover:bg-slate-100 dark:bg-white/5 border border-transparent hover:border-slate-200 dark:border-white/10 cursor-pointer transition-all w-full text-left">
<div className={`w-8 h-8 rounded-lg flex items-center justify-center shrink-0 border ${
isCoin ? 'bg-amber-500/10 border-amber-500/20 text-amber-500' : 'bg-blue-500/10 border-blue-500/20 text-blue-500'
}`}>
{isCoin ? <Coins size={14} /> : <Box size={14} />}
</div>
<div className="flex-1 min-w-0">
<div className="flex justify-between items-center mb-0.5">
<span className="text-xs font-bold text-slate-600 dark:text-slate-300 truncate">{shortType || 'Object'}</span>
<span className="text-9px font-mono text-slate-600">v{obj.data?.version}</span>
</div>
<div className="text-[10px] font-mono text-slate-600 truncate opacity-60 group-hover:opacity-100">{obj.data?.objectId}</div>
</div>
</button>
);
})}
{objects.length === 0 && !loadingObjects && (
<div className="p-8 text-center opacity-50">
<Box size={24} className="mx-auto mb-2 text-slate-600"/>
<p className="text-xs text-slate-500">No objects found</p>
</div>
)}
</div>
</>
);
};