@jaw.id/ui 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs-error.cjs +3 -0
- package/dist/{ccip-BYa9WTP9.js → ccip-yRQNp5zr.js} +1 -1
- package/dist/{index-DBYNWeek.js → index-BcDVfcdq.js} +20832 -20511
- package/dist/index.js +1 -1
- package/package.json +8 -2
- package/.babelrc +0 -12
- package/CHANGELOG.md +0 -23
- package/components.json +0 -22
- package/postcss.config.cjs +0 -6
- package/src/components/ConnectDialog/index.tsx +0 -270
- package/src/components/ConnectDialog/types.ts +0 -34
- package/src/components/DefaultDialog/index.tsx +0 -99
- package/src/components/Eip712Dialog/index.tsx +0 -525
- package/src/components/Eip712Dialog/types.ts +0 -27
- package/src/components/FeeTokenSelector/index.tsx +0 -308
- package/src/components/OnboardingDialog/index.tsx +0 -317
- package/src/components/OnboardingDialog/types.ts +0 -43
- package/src/components/OrSeparator/index.tsx +0 -13
- package/src/components/PermissionDialog/index.tsx +0 -598
- package/src/components/PermissionDialog/types.ts +0 -77
- package/src/components/SignatureDialog/index.tsx +0 -180
- package/src/components/SignatureDialog/types.ts +0 -27
- package/src/components/SiweDialog/index.tsx +0 -231
- package/src/components/SiweDialog/types.ts +0 -34
- package/src/components/TransactionDialog/DecodedCalldata.tsx +0 -79
- package/src/components/TransactionDialog/index.tsx +0 -663
- package/src/components/TransactionDialog/types.ts +0 -54
- package/src/components/ui/accordion.tsx +0 -66
- package/src/components/ui/avatar.tsx +0 -53
- package/src/components/ui/button.tsx +0 -59
- package/src/components/ui/card.tsx +0 -92
- package/src/components/ui/checkbox.tsx +0 -32
- package/src/components/ui/dialog.tsx +0 -183
- package/src/components/ui/dropdown-menu.tsx +0 -258
- package/src/components/ui/form.tsx +0 -167
- package/src/components/ui/input.tsx +0 -60
- package/src/components/ui/label.tsx +0 -24
- package/src/components/ui/popover.tsx +0 -48
- package/src/components/ui/scroll-area.tsx +0 -58
- package/src/components/ui/select.tsx +0 -160
- package/src/components/ui/separator.tsx +0 -28
- package/src/components/ui/sheet.tsx +0 -169
- package/src/components/ui/spinner.tsx +0 -18
- package/src/components/ui/tabs.tsx +0 -69
- package/src/components/ui/tooltip.tsx +0 -61
- package/src/hooks/index.ts +0 -5
- package/src/hooks/useChainIconURI.tsx +0 -117
- package/src/hooks/useDecodedCalldata.ts +0 -128
- package/src/hooks/useFeeTokenPrice.tsx +0 -36
- package/src/hooks/useGasEstimation.ts +0 -474
- package/src/hooks/useIsMobile.ts +0 -36
- package/src/icons/index.tsx +0 -114
- package/src/index.ts +0 -19
- package/src/lib/utils.ts +0 -6
- package/src/react/ReactUIHandler.tsx +0 -3004
- package/src/react/index.ts +0 -2
- package/src/styles.css +0 -210
- package/src/utils/formatAddress.ts +0 -24
- package/src/utils/index.ts +0 -4
- package/src/utils/justaNameInstance.ts +0 -25
- package/src/utils/tokenBalance.ts +0 -41
- package/src/utils/tokenPrice.ts +0 -58
- package/tailwind.config.js +0 -130
- package/tsconfig.json +0 -19
- package/tsconfig.lib.json +0 -43
- package/vite.config.ts +0 -45
|
@@ -1,525 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import { Button } from "../ui/button";
|
|
4
|
-
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "../ui/accordion";
|
|
5
|
-
import { DefaultDialog } from "../DefaultDialog";
|
|
6
|
-
import { Eip712DialogProps } from "./types";
|
|
7
|
-
import { useIsMobile } from "../../hooks";
|
|
8
|
-
import { getJustaNameInstance, getDisplayAddress } from "../../utils";
|
|
9
|
-
import { useState, useEffect, useMemo, useRef } from "react";
|
|
10
|
-
|
|
11
|
-
// EIP-712 TypedData structure
|
|
12
|
-
interface TypedData {
|
|
13
|
-
types: Record<string, Array<{ name: string; type: string }>>;
|
|
14
|
-
primaryType: string;
|
|
15
|
-
domain: Record<string, unknown>;
|
|
16
|
-
message: Record<string, unknown>;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const isObject = (value: unknown): value is Record<string, unknown> => {
|
|
20
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
const isArray = (value: unknown): value is unknown[] => {
|
|
24
|
-
return Array.isArray(value);
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
const getValueColor = (value: unknown): string => {
|
|
28
|
-
if (typeof value === 'string') return 'text-foreground';
|
|
29
|
-
if (typeof value === 'number') return 'text-blue-600 dark:text-blue-400';
|
|
30
|
-
if (typeof value === 'boolean') return 'text-purple-600 dark:text-purple-400';
|
|
31
|
-
if (value === null || value === undefined) return 'text-gray-500 dark:text-gray-400';
|
|
32
|
-
return 'text-foreground';
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const formatPrimitiveValue = (value: unknown): { text: string; color: string } => {
|
|
36
|
-
if (value === null) return { text: 'null', color: getValueColor(null) };
|
|
37
|
-
if (value === undefined) return { text: 'undefined', color: getValueColor(undefined) };
|
|
38
|
-
if (typeof value === 'boolean') return { text: String(value), color: getValueColor(value) };
|
|
39
|
-
if (typeof value === 'number') return { text: String(value), color: getValueColor(value) };
|
|
40
|
-
if (typeof value === 'string') return { text: `"${value}"`, color: getValueColor(value) };
|
|
41
|
-
return { text: JSON.stringify(value), color: 'text-foreground' };
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
// Component for rendering a single property line
|
|
45
|
-
const PropertyLine = ({
|
|
46
|
-
propertyKey,
|
|
47
|
-
value,
|
|
48
|
-
isLast,
|
|
49
|
-
depth
|
|
50
|
-
}: {
|
|
51
|
-
propertyKey: string;
|
|
52
|
-
value: unknown;
|
|
53
|
-
isLast: boolean;
|
|
54
|
-
depth: number;
|
|
55
|
-
}) => {
|
|
56
|
-
const formatted = formatPrimitiveValue(value);
|
|
57
|
-
const paddingLeft = depth * 16; // 16px per depth level
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div
|
|
61
|
-
className="flex items-start gap-1 py-0.5 font-mono text-sm"
|
|
62
|
-
style={{ paddingLeft: `${paddingLeft}px` }}
|
|
63
|
-
>
|
|
64
|
-
<span className="text-muted-foreground">"{propertyKey}":</span>
|
|
65
|
-
<span className={formatted.color}>{formatted.text}</span>
|
|
66
|
-
{!isLast && <span className="text-muted-foreground">,</span>}
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// Recursive component to render nested objects
|
|
72
|
-
const NestedDataView = ({
|
|
73
|
-
data,
|
|
74
|
-
depth = 0,
|
|
75
|
-
parentKey,
|
|
76
|
-
isLast = true
|
|
77
|
-
}: {
|
|
78
|
-
data: unknown;
|
|
79
|
-
depth?: number;
|
|
80
|
-
parentKey?: string;
|
|
81
|
-
isLast?: boolean;
|
|
82
|
-
}) => {
|
|
83
|
-
const paddingLeft = depth * 16;
|
|
84
|
-
|
|
85
|
-
if (isObject(data)) {
|
|
86
|
-
const entries = Object.entries(data);
|
|
87
|
-
const accordionId = `${parentKey || 'root'}-${depth}`;
|
|
88
|
-
|
|
89
|
-
if (depth === 0) {
|
|
90
|
-
return (
|
|
91
|
-
<div className="w-full font-mono text-sm">
|
|
92
|
-
<div className="flex flex-col">
|
|
93
|
-
{entries.map(([key, value], index) => {
|
|
94
|
-
const isLastEntry = index === entries.length - 1;
|
|
95
|
-
|
|
96
|
-
if (isObject(value) || isArray(value)) {
|
|
97
|
-
return (
|
|
98
|
-
<NestedDataView
|
|
99
|
-
key={key}
|
|
100
|
-
data={value}
|
|
101
|
-
depth={depth + 1}
|
|
102
|
-
parentKey={key}
|
|
103
|
-
isLast={isLastEntry}
|
|
104
|
-
/>
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
return (
|
|
109
|
-
<PropertyLine
|
|
110
|
-
key={key}
|
|
111
|
-
propertyKey={key}
|
|
112
|
-
value={value}
|
|
113
|
-
isLast={isLastEntry}
|
|
114
|
-
depth={depth}
|
|
115
|
-
/>
|
|
116
|
-
);
|
|
117
|
-
})}
|
|
118
|
-
</div>
|
|
119
|
-
</div>
|
|
120
|
-
);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return (
|
|
124
|
-
<div className="w-full font-mono text-sm">
|
|
125
|
-
<Accordion type="multiple" className="w-full" defaultValue={[]}>
|
|
126
|
-
<AccordionItem value={accordionId} className="border-none">
|
|
127
|
-
<div style={{ paddingLeft: `${paddingLeft}px` }}>
|
|
128
|
-
<AccordionTrigger className="py-0.5 hover:no-underline hover:opacity-70 transition-opacity cursor-pointer [&>svg]:hidden group">
|
|
129
|
-
<span className="flex items-center gap-0.5">
|
|
130
|
-
{parentKey && <span className="text-muted-foreground">"{parentKey}":</span>}
|
|
131
|
-
<span className="text-muted-foreground group-data-[state=closed]:inline hidden">{' {...}'}</span>
|
|
132
|
-
<span className="text-muted-foreground group-data-[state=open]:inline hidden">{' {'}</span>
|
|
133
|
-
{!isLast && <span className="text-muted-foreground group-data-[state=closed]:inline hidden">,</span>}
|
|
134
|
-
</span>
|
|
135
|
-
</AccordionTrigger>
|
|
136
|
-
</div>
|
|
137
|
-
<AccordionContent className="pb-0 pt-0">
|
|
138
|
-
<div className="flex flex-col">
|
|
139
|
-
{entries.map(([key, value], index) => {
|
|
140
|
-
const isLastEntry = index === entries.length - 1;
|
|
141
|
-
|
|
142
|
-
if (isObject(value) || isArray(value)) {
|
|
143
|
-
return (
|
|
144
|
-
<NestedDataView
|
|
145
|
-
key={key}
|
|
146
|
-
data={value}
|
|
147
|
-
depth={depth + 1}
|
|
148
|
-
parentKey={key}
|
|
149
|
-
isLast={isLastEntry}
|
|
150
|
-
/>
|
|
151
|
-
);
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
return (
|
|
155
|
-
<PropertyLine
|
|
156
|
-
key={key}
|
|
157
|
-
propertyKey={key}
|
|
158
|
-
value={value}
|
|
159
|
-
isLast={isLastEntry}
|
|
160
|
-
depth={depth + 1}
|
|
161
|
-
/>
|
|
162
|
-
);
|
|
163
|
-
})}
|
|
164
|
-
<div
|
|
165
|
-
className="py-0.5 text-muted-foreground"
|
|
166
|
-
style={{ paddingLeft: `${paddingLeft}px` }}
|
|
167
|
-
>
|
|
168
|
-
<span>{'}'}</span>
|
|
169
|
-
{!isLast && <span>,</span>}
|
|
170
|
-
</div>
|
|
171
|
-
</div>
|
|
172
|
-
</AccordionContent>
|
|
173
|
-
</AccordionItem>
|
|
174
|
-
</Accordion>
|
|
175
|
-
</div>
|
|
176
|
-
);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
if (isArray(data)) {
|
|
180
|
-
const accordionId = `${parentKey || 'array'}-${depth}`;
|
|
181
|
-
|
|
182
|
-
if (depth === 0) {
|
|
183
|
-
return (
|
|
184
|
-
<div className="w-full font-mono text-sm">
|
|
185
|
-
<div className="flex flex-col">
|
|
186
|
-
{data.map((item, index) => {
|
|
187
|
-
const isLastEntry = index === data.length - 1;
|
|
188
|
-
const indexKey = `[${index}]`;
|
|
189
|
-
|
|
190
|
-
if (isObject(item) || isArray(item)) {
|
|
191
|
-
return (
|
|
192
|
-
<div key={index}>
|
|
193
|
-
<div
|
|
194
|
-
className="py-0.5 text-muted-foreground font-mono text-sm"
|
|
195
|
-
style={{ paddingLeft: `${depth * 16}px` }}
|
|
196
|
-
>
|
|
197
|
-
{indexKey}:
|
|
198
|
-
</div>
|
|
199
|
-
<NestedDataView
|
|
200
|
-
data={item}
|
|
201
|
-
depth={depth + 1}
|
|
202
|
-
isLast={isLastEntry}
|
|
203
|
-
/>
|
|
204
|
-
</div>
|
|
205
|
-
);
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const formatted = formatPrimitiveValue(item);
|
|
209
|
-
return (
|
|
210
|
-
<div
|
|
211
|
-
key={index}
|
|
212
|
-
className="flex items-start gap-1 py-0.5 font-mono text-sm"
|
|
213
|
-
style={{ paddingLeft: `${depth * 16}px` }}
|
|
214
|
-
>
|
|
215
|
-
<span className="text-muted-foreground">{indexKey}:</span>
|
|
216
|
-
<span className={formatted.color}>{formatted.text}</span>
|
|
217
|
-
{!isLastEntry && <span className="text-muted-foreground">,</span>}
|
|
218
|
-
</div>
|
|
219
|
-
);
|
|
220
|
-
})}
|
|
221
|
-
</div>
|
|
222
|
-
</div>
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return (
|
|
227
|
-
<div className="w-full font-mono text-sm">
|
|
228
|
-
<Accordion type="multiple" className="w-full" defaultValue={[]}>
|
|
229
|
-
<AccordionItem value={accordionId} className="border-none">
|
|
230
|
-
<div style={{ paddingLeft: `${paddingLeft}px` }}>
|
|
231
|
-
<AccordionTrigger className="py-0.5 hover:no-underline hover:opacity-70 transition-opacity cursor-pointer [&>svg]:hidden group">
|
|
232
|
-
<span className="flex items-center gap-0.5">
|
|
233
|
-
{parentKey && <span className="text-muted-foreground">"{parentKey}":</span>}
|
|
234
|
-
<span className="text-muted-foreground group-data-[state=closed]:inline hidden">{' [...]'}</span>
|
|
235
|
-
<span className="text-muted-foreground group-data-[state=open]:inline hidden">{' ['}</span>
|
|
236
|
-
{!isLast && <span className="text-muted-foreground group-data-[state=closed]:inline hidden">,</span>}
|
|
237
|
-
</span>
|
|
238
|
-
</AccordionTrigger>
|
|
239
|
-
</div>
|
|
240
|
-
<AccordionContent className="pb-0 pt-0">
|
|
241
|
-
<div className="flex flex-col">
|
|
242
|
-
{data.map((item, index) => {
|
|
243
|
-
const isLastEntry = index === data.length - 1;
|
|
244
|
-
const indexKey = `[${index}]`;
|
|
245
|
-
|
|
246
|
-
if (isObject(item) || isArray(item)) {
|
|
247
|
-
return (
|
|
248
|
-
<div key={index}>
|
|
249
|
-
<div
|
|
250
|
-
className="py-0.5 text-muted-foreground font-mono text-sm"
|
|
251
|
-
style={{ paddingLeft: `${(depth + 1) * 16}px` }}
|
|
252
|
-
>
|
|
253
|
-
{indexKey}:
|
|
254
|
-
</div>
|
|
255
|
-
<NestedDataView
|
|
256
|
-
data={item}
|
|
257
|
-
depth={depth + 2}
|
|
258
|
-
isLast={isLastEntry}
|
|
259
|
-
/>
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
const formatted = formatPrimitiveValue(item);
|
|
265
|
-
return (
|
|
266
|
-
<div
|
|
267
|
-
key={index}
|
|
268
|
-
className="flex items-start gap-1 py-0.5 font-mono text-sm"
|
|
269
|
-
style={{ paddingLeft: `${(depth + 1) * 16}px` }}
|
|
270
|
-
>
|
|
271
|
-
<span className="text-muted-foreground">{indexKey}:</span>
|
|
272
|
-
<span className={formatted.color}>{formatted.text}</span>
|
|
273
|
-
{!isLastEntry && <span className="text-muted-foreground">,</span>}
|
|
274
|
-
</div>
|
|
275
|
-
);
|
|
276
|
-
})}
|
|
277
|
-
<div
|
|
278
|
-
className="py-0.5 text-muted-foreground"
|
|
279
|
-
style={{ paddingLeft: `${paddingLeft}px` }}
|
|
280
|
-
>
|
|
281
|
-
<span>{']'}</span>
|
|
282
|
-
{!isLast && <span>,</span>}
|
|
283
|
-
</div>
|
|
284
|
-
</div>
|
|
285
|
-
</AccordionContent>
|
|
286
|
-
</AccordionItem>
|
|
287
|
-
</Accordion>
|
|
288
|
-
</div>
|
|
289
|
-
);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const formatted = formatPrimitiveValue(data);
|
|
293
|
-
return (
|
|
294
|
-
<div className="py-0.5 font-mono text-sm" style={{ paddingLeft: `${paddingLeft}px` }}>
|
|
295
|
-
<span className={formatted.color}>{formatted.text}</span>
|
|
296
|
-
</div>
|
|
297
|
-
);
|
|
298
|
-
};
|
|
299
|
-
|
|
300
|
-
export const Eip712Dialog = ({
|
|
301
|
-
open,
|
|
302
|
-
onOpenChange,
|
|
303
|
-
typedDataJson,
|
|
304
|
-
origin,
|
|
305
|
-
timestamp,
|
|
306
|
-
accountAddress,
|
|
307
|
-
chainName,
|
|
308
|
-
chainId,
|
|
309
|
-
chainIcon,
|
|
310
|
-
mainnetRpcUrl,
|
|
311
|
-
onSign,
|
|
312
|
-
onCancel,
|
|
313
|
-
isProcessing,
|
|
314
|
-
signatureStatus,
|
|
315
|
-
canSign,
|
|
316
|
-
}: Eip712DialogProps) => {
|
|
317
|
-
// Ref for scrollable container
|
|
318
|
-
const scrollableRef = useRef<HTMLDivElement>(null);
|
|
319
|
-
|
|
320
|
-
const isMobile = useIsMobile();
|
|
321
|
-
const [resolvedAddress, setResolvedAddress] = useState<string | null>(null);
|
|
322
|
-
|
|
323
|
-
// Parse typed data
|
|
324
|
-
const typedData = useMemo(() => {
|
|
325
|
-
try {
|
|
326
|
-
return JSON.parse(typedDataJson) as TypedData;
|
|
327
|
-
} catch (error) {
|
|
328
|
-
console.error('Failed to parse typed data:', error);
|
|
329
|
-
return null;
|
|
330
|
-
}
|
|
331
|
-
}, [typedDataJson]);
|
|
332
|
-
|
|
333
|
-
// Resolve account address to human-readable name
|
|
334
|
-
useEffect(() => {
|
|
335
|
-
if (accountAddress && chainId) {
|
|
336
|
-
const justaName = getJustaNameInstance(mainnetRpcUrl);
|
|
337
|
-
justaName.subnames.reverseResolve({
|
|
338
|
-
address: accountAddress as `0x${string}`,
|
|
339
|
-
chainId: chainId,
|
|
340
|
-
}).then((result) => {
|
|
341
|
-
if (result) {
|
|
342
|
-
setResolvedAddress(result);
|
|
343
|
-
}
|
|
344
|
-
}).catch(() => {
|
|
345
|
-
// Silently fail if resolution fails
|
|
346
|
-
});
|
|
347
|
-
}
|
|
348
|
-
}, [accountAddress, chainId]);
|
|
349
|
-
|
|
350
|
-
// Handle wheel events for smooth scrolling over JSON content
|
|
351
|
-
useEffect(() => {
|
|
352
|
-
if (!open) return;
|
|
353
|
-
|
|
354
|
-
let cleanupFn: (() => void) | null = null;
|
|
355
|
-
|
|
356
|
-
// Use a small delay to ensure the DOM is ready
|
|
357
|
-
const timer = setTimeout(() => {
|
|
358
|
-
const scrollable = scrollableRef.current;
|
|
359
|
-
if (!scrollable) return;
|
|
360
|
-
|
|
361
|
-
const handleWheel = (e: WheelEvent) => {
|
|
362
|
-
e.preventDefault();
|
|
363
|
-
scrollable.scrollTop += e.deltaY;
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
scrollable.addEventListener('wheel', handleWheel, { passive: false });
|
|
367
|
-
|
|
368
|
-
cleanupFn = () => {
|
|
369
|
-
scrollable.removeEventListener('wheel', handleWheel);
|
|
370
|
-
};
|
|
371
|
-
}, 100);
|
|
372
|
-
|
|
373
|
-
return () => {
|
|
374
|
-
clearTimeout(timer);
|
|
375
|
-
if (cleanupFn) cleanupFn();
|
|
376
|
-
};
|
|
377
|
-
}, [open]);
|
|
378
|
-
|
|
379
|
-
// Get display address - use resolved name or formatted address
|
|
380
|
-
const displayAddress = getDisplayAddress(resolvedAddress, accountAddress || '');
|
|
381
|
-
|
|
382
|
-
// Format origin to display only domain (remove protocol)
|
|
383
|
-
const formatOrigin = (url: string) => {
|
|
384
|
-
try {
|
|
385
|
-
const urlObj = new URL(url.startsWith('http') ? url : `https://${url}`);
|
|
386
|
-
return urlObj.hostname.replace('www.', '');
|
|
387
|
-
} catch {
|
|
388
|
-
return origin;
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
|
|
392
|
-
// Get contract address from domain
|
|
393
|
-
const contractAddress = typedData?.domain?.verifyingContract as string | undefined;
|
|
394
|
-
const domainName = typedData?.domain?.name as string | undefined;
|
|
395
|
-
|
|
396
|
-
return (
|
|
397
|
-
<DefaultDialog
|
|
398
|
-
open={open}
|
|
399
|
-
onOpenChange={!isProcessing ? onOpenChange : undefined}
|
|
400
|
-
header={
|
|
401
|
-
<div className="flex flex-col gap-2.5 p-3.5">
|
|
402
|
-
<p className="text-xs font-bold text-muted-foreground leading-[100%]">
|
|
403
|
-
{timestamp.toLocaleDateString('en-US', {
|
|
404
|
-
weekday: 'long',
|
|
405
|
-
day: 'numeric',
|
|
406
|
-
month: 'long'
|
|
407
|
-
})} at {timestamp.toLocaleTimeString('en-US', {
|
|
408
|
-
hour: '2-digit',
|
|
409
|
-
minute: '2-digit',
|
|
410
|
-
second: '2-digit',
|
|
411
|
-
timeZoneName: 'short'
|
|
412
|
-
})}
|
|
413
|
-
</p>
|
|
414
|
-
<p className="text-[30px] font-normal leading-[100%] text-foreground">
|
|
415
|
-
Review
|
|
416
|
-
</p>
|
|
417
|
-
<p className="text-xs font-normal leading-[100%] text-foreground">{displayAddress}</p>
|
|
418
|
-
</div>
|
|
419
|
-
}
|
|
420
|
-
contentStyle={isMobile ? {
|
|
421
|
-
width: '100%',
|
|
422
|
-
height: '100%',
|
|
423
|
-
maxWidth: 'none',
|
|
424
|
-
maxHeight: 'none',
|
|
425
|
-
overflowY: 'auto',
|
|
426
|
-
} : {
|
|
427
|
-
width: '500px',
|
|
428
|
-
minWidth: '500px',
|
|
429
|
-
}}
|
|
430
|
-
>
|
|
431
|
-
<div className="flex flex-col gap-6 justify-between max-md:h-full">
|
|
432
|
-
{/* Main Content - Typed Data Tree View */}
|
|
433
|
-
<div className="flex flex-col gap-3 max-md:flex-1 max-h-[60vh] overflow-y-auto min-h-0">
|
|
434
|
-
{typedData ? (
|
|
435
|
-
<div ref={scrollableRef} className="max-h-[50vh] flex flex-1 overflow-y-auto bg-muted/30 dark:bg-muted/10 rounded-[6px] p-3 border border-border">
|
|
436
|
-
{/* Combine domain and message into single tree */}
|
|
437
|
-
<NestedDataView
|
|
438
|
-
data={
|
|
439
|
-
typedData
|
|
440
|
-
}
|
|
441
|
-
depth={0}
|
|
442
|
-
/>
|
|
443
|
-
</div>
|
|
444
|
-
) : (
|
|
445
|
-
<div className="p-4 bg-red-50 border border-red-200 rounded-[6px]">
|
|
446
|
-
<p className="text-sm text-red-600">Failed to parse typed data</p>
|
|
447
|
-
</div>
|
|
448
|
-
)}
|
|
449
|
-
|
|
450
|
-
{/* URL and Domain Information */}
|
|
451
|
-
<div className="flex flex-row justify-between items-center gap-2.5 p-3.5 border border-border rounded-[6px] max-md:mt-auto">
|
|
452
|
-
<div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
|
|
453
|
-
<p className="text-xs font-bold leading-[133%]">URL</p>
|
|
454
|
-
<p className="text-base font-normal leading-[150%] truncate">
|
|
455
|
-
{formatOrigin(origin)}
|
|
456
|
-
</p>
|
|
457
|
-
</div>
|
|
458
|
-
<div className="w-[1px] rounded-full bg-border h-full flex-shrink-0 min-h-[50px]" />
|
|
459
|
-
<div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
|
|
460
|
-
<p className="text-xs font-bold leading-[133%]">Domain</p>
|
|
461
|
-
<p className="text-base font-normal leading-[150%] truncate">
|
|
462
|
-
{domainName || formatOrigin(origin)}
|
|
463
|
-
</p>
|
|
464
|
-
</div>
|
|
465
|
-
</div>
|
|
466
|
-
|
|
467
|
-
{/* Contract Address if available */}
|
|
468
|
-
{contractAddress && (
|
|
469
|
-
<div className="flex flex-row justify-between items-center gap-2.5 p-3.5 border border-border rounded-[6px]">
|
|
470
|
-
<div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
|
|
471
|
-
<p className="text-xs font-bold leading-[133%]">Contract</p>
|
|
472
|
-
<p className="text-base font-normal leading-[150%] truncate">
|
|
473
|
-
{contractAddress}
|
|
474
|
-
</p>
|
|
475
|
-
</div>
|
|
476
|
-
{chainName && (
|
|
477
|
-
<>
|
|
478
|
-
<div className="w-[1px] rounded-full bg-border h-full flex-shrink-0 min-h-[50px]" />
|
|
479
|
-
<div className="flex flex-col text-foreground gap-0.5 min-w-0 flex-1">
|
|
480
|
-
<p className="text-xs font-bold leading-[133%]">Network</p>
|
|
481
|
-
<div className="flex flex-row items-center gap-1 min-w-0">
|
|
482
|
-
{chainIcon && <div className="w-6 h-6 flex-shrink-0">{chainIcon}</div>}
|
|
483
|
-
<p className="text-base font-normal leading-[150%] truncate">{chainName}</p>
|
|
484
|
-
</div>
|
|
485
|
-
</div>
|
|
486
|
-
</>
|
|
487
|
-
)}
|
|
488
|
-
</div>
|
|
489
|
-
)}
|
|
490
|
-
|
|
491
|
-
{/* Status Message */}
|
|
492
|
-
{signatureStatus && (
|
|
493
|
-
<div className={`text-sm p-3 rounded-lg ${signatureStatus.includes('Error') ? 'bg-red-50 text-red-600' :
|
|
494
|
-
signatureStatus.includes('successfully') ? 'bg-green-50 text-green-600' :
|
|
495
|
-
'bg-blue-50 text-blue-600'
|
|
496
|
-
}`}>
|
|
497
|
-
{signatureStatus}
|
|
498
|
-
</div>
|
|
499
|
-
)}
|
|
500
|
-
</div>
|
|
501
|
-
|
|
502
|
-
{/* Action Buttons */}
|
|
503
|
-
<div className="flex gap-3 p-3.5 flex-shrink-0">
|
|
504
|
-
<Button
|
|
505
|
-
variant="outline"
|
|
506
|
-
onClick={onCancel}
|
|
507
|
-
disabled={isProcessing}
|
|
508
|
-
className="flex-1"
|
|
509
|
-
>
|
|
510
|
-
Cancel
|
|
511
|
-
</Button>
|
|
512
|
-
<Button
|
|
513
|
-
onClick={onSign}
|
|
514
|
-
disabled={!canSign}
|
|
515
|
-
className="flex-1"
|
|
516
|
-
>
|
|
517
|
-
{isProcessing ? 'Processing...' : 'Sign'}
|
|
518
|
-
</Button>
|
|
519
|
-
</div>
|
|
520
|
-
</div>
|
|
521
|
-
</DefaultDialog>
|
|
522
|
-
)
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
export * from './types';
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { JSX } from "react";
|
|
2
|
-
export interface Eip712DialogProps {
|
|
3
|
-
open: boolean;
|
|
4
|
-
onOpenChange: (open: boolean) => void;
|
|
5
|
-
|
|
6
|
-
// EIP-712 typed data (JSON string)
|
|
7
|
-
typedDataJson: string;
|
|
8
|
-
origin: string;
|
|
9
|
-
timestamp: Date;
|
|
10
|
-
|
|
11
|
-
accountAddress?: string;
|
|
12
|
-
chainName?: string;
|
|
13
|
-
chainId?: number;
|
|
14
|
-
chainIcon?: JSX.Element;
|
|
15
|
-
|
|
16
|
-
// Actions
|
|
17
|
-
onSign: () => Promise<void>;
|
|
18
|
-
onCancel: () => void;
|
|
19
|
-
|
|
20
|
-
// Status
|
|
21
|
-
isProcessing: boolean;
|
|
22
|
-
signatureStatus: string;
|
|
23
|
-
canSign: boolean;
|
|
24
|
-
|
|
25
|
-
// RPC configuration
|
|
26
|
-
mainnetRpcUrl: string;
|
|
27
|
-
}
|