@thehoneyjar/sigil-hud 0.1.0
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/LICENSE.md +660 -0
- package/README.md +146 -0
- package/dist/index.d.ts +911 -0
- package/dist/index.js +3079 -0
- package/package.json +68 -0
- package/src/components/DataSourceIndicator.tsx +185 -0
- package/src/components/DiagnosticsPanel.tsx +444 -0
- package/src/components/FeedbackPrompt.tsx +348 -0
- package/src/components/HudPanel.tsx +179 -0
- package/src/components/HudTrigger.tsx +81 -0
- package/src/components/IssueList.tsx +228 -0
- package/src/components/LensPanel.tsx +286 -0
- package/src/components/ObservationCaptureModal.tsx +502 -0
- package/src/components/PhysicsAnalysis.tsx +273 -0
- package/src/components/SimulationPanel.tsx +173 -0
- package/src/components/StateComparison.tsx +238 -0
- package/src/hooks/useDataSource.ts +324 -0
- package/src/hooks/useKeyboardShortcuts.ts +125 -0
- package/src/hooks/useObservationCapture.ts +154 -0
- package/src/hooks/useSignalCapture.ts +138 -0
- package/src/index.ts +112 -0
- package/src/providers/HudProvider.tsx +115 -0
- package/src/store.ts +60 -0
- package/src/styles/theme.ts +256 -0
- package/src/types.ts +276 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue List Component
|
|
3
|
+
*
|
|
4
|
+
* Displays detected diagnostic issues with severity and suggestions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { DiagnosticIssue } from '../types'
|
|
8
|
+
|
|
9
|
+
type Severity = 'error' | 'warning' | 'info'
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Props for IssueList
|
|
13
|
+
*/
|
|
14
|
+
export interface IssueListProps {
|
|
15
|
+
/** Issues to display */
|
|
16
|
+
issues: DiagnosticIssue[]
|
|
17
|
+
/** Maximum issues to show (expandable) */
|
|
18
|
+
maxVisible?: number
|
|
19
|
+
/** Callback when issue is clicked */
|
|
20
|
+
onIssueClick?: (issue: DiagnosticIssue) => void
|
|
21
|
+
/** Custom class name */
|
|
22
|
+
className?: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Severity colors
|
|
27
|
+
*/
|
|
28
|
+
const severityColors: Record<Severity, { bg: string; border: string; text: string }> = {
|
|
29
|
+
error: { bg: 'rgba(239, 68, 68, 0.1)', border: 'rgba(239, 68, 68, 0.3)', text: '#ef4444' },
|
|
30
|
+
warning: { bg: 'rgba(234, 179, 8, 0.1)', border: 'rgba(234, 179, 8, 0.3)', text: '#eab308' },
|
|
31
|
+
info: { bg: 'rgba(59, 130, 246, 0.1)', border: 'rgba(59, 130, 246, 0.3)', text: '#3b82f6' },
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Severity icons
|
|
36
|
+
*/
|
|
37
|
+
const severityIcons: Record<Severity, string> = {
|
|
38
|
+
error: '✕',
|
|
39
|
+
warning: '⚠',
|
|
40
|
+
info: 'ℹ',
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Issue List component
|
|
45
|
+
*/
|
|
46
|
+
export function IssueList({
|
|
47
|
+
issues,
|
|
48
|
+
maxVisible = 5,
|
|
49
|
+
onIssueClick,
|
|
50
|
+
className = '',
|
|
51
|
+
}: IssueListProps) {
|
|
52
|
+
const hasMore = issues.length > maxVisible
|
|
53
|
+
const visibleIssues = hasMore ? issues.slice(0, maxVisible) : issues
|
|
54
|
+
|
|
55
|
+
if (issues.length === 0) {
|
|
56
|
+
return (
|
|
57
|
+
<div className={className} style={styles.container}>
|
|
58
|
+
<div style={styles.header}>
|
|
59
|
+
<span style={styles.title}>Issues</span>
|
|
60
|
+
<span style={styles.count}>0</span>
|
|
61
|
+
</div>
|
|
62
|
+
<div style={styles.empty}>
|
|
63
|
+
<span style={styles.checkIcon}>✓</span>
|
|
64
|
+
No issues detected
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Count by severity
|
|
71
|
+
const errorCount = issues.filter((i) => i.severity === 'error').length
|
|
72
|
+
const warningCount = issues.filter((i) => i.severity === 'warning').length
|
|
73
|
+
|
|
74
|
+
return (
|
|
75
|
+
<div className={className} style={styles.container}>
|
|
76
|
+
{/* Header */}
|
|
77
|
+
<div style={styles.header}>
|
|
78
|
+
<span style={styles.title}>Issues</span>
|
|
79
|
+
<div style={styles.counts}>
|
|
80
|
+
{errorCount > 0 && (
|
|
81
|
+
<span style={{ ...styles.countBadge, ...severityColors.error }}>
|
|
82
|
+
{errorCount} error{errorCount !== 1 ? 's' : ''}
|
|
83
|
+
</span>
|
|
84
|
+
)}
|
|
85
|
+
{warningCount > 0 && (
|
|
86
|
+
<span style={{ ...styles.countBadge, ...severityColors.warning }}>
|
|
87
|
+
{warningCount} warning{warningCount !== 1 ? 's' : ''}
|
|
88
|
+
</span>
|
|
89
|
+
)}
|
|
90
|
+
</div>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* Issue List */}
|
|
94
|
+
<div style={styles.list}>
|
|
95
|
+
{visibleIssues.map((issue, index) => {
|
|
96
|
+
const colors = severityColors[issue.severity]
|
|
97
|
+
const icon = severityIcons[issue.severity]
|
|
98
|
+
|
|
99
|
+
return (
|
|
100
|
+
<button
|
|
101
|
+
key={`${issue.code}-${index}`}
|
|
102
|
+
onClick={() => onIssueClick?.(issue)}
|
|
103
|
+
style={{
|
|
104
|
+
...styles.issue,
|
|
105
|
+
backgroundColor: colors.bg,
|
|
106
|
+
borderColor: colors.border,
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<span style={{ ...styles.icon, color: colors.text }}>{icon}</span>
|
|
110
|
+
<div style={styles.issueContent}>
|
|
111
|
+
<span style={styles.issueCode}>[{issue.code}]</span>
|
|
112
|
+
<span style={styles.issueMessage}>{issue.message}</span>
|
|
113
|
+
{issue.suggestion && (
|
|
114
|
+
<span style={styles.issueSuggestion}>Fix: {issue.suggestion}</span>
|
|
115
|
+
)}
|
|
116
|
+
</div>
|
|
117
|
+
</button>
|
|
118
|
+
)
|
|
119
|
+
})}
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
{/* More indicator */}
|
|
123
|
+
{hasMore && (
|
|
124
|
+
<div style={styles.more}>+ {issues.length - maxVisible} more issues</div>
|
|
125
|
+
)}
|
|
126
|
+
</div>
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Styles
|
|
132
|
+
*/
|
|
133
|
+
const styles: Record<string, React.CSSProperties> = {
|
|
134
|
+
container: {
|
|
135
|
+
padding: '12px',
|
|
136
|
+
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
137
|
+
borderRadius: '8px',
|
|
138
|
+
border: '1px solid rgba(255, 255, 255, 0.05)',
|
|
139
|
+
},
|
|
140
|
+
header: {
|
|
141
|
+
display: 'flex',
|
|
142
|
+
justifyContent: 'space-between',
|
|
143
|
+
alignItems: 'center',
|
|
144
|
+
marginBottom: '12px',
|
|
145
|
+
},
|
|
146
|
+
title: {
|
|
147
|
+
fontSize: '11px',
|
|
148
|
+
fontWeight: 600,
|
|
149
|
+
color: '#888',
|
|
150
|
+
textTransform: 'uppercase',
|
|
151
|
+
letterSpacing: '0.5px',
|
|
152
|
+
},
|
|
153
|
+
count: {
|
|
154
|
+
fontSize: '10px',
|
|
155
|
+
color: '#666',
|
|
156
|
+
},
|
|
157
|
+
counts: {
|
|
158
|
+
display: 'flex',
|
|
159
|
+
gap: '6px',
|
|
160
|
+
},
|
|
161
|
+
countBadge: {
|
|
162
|
+
padding: '2px 6px',
|
|
163
|
+
borderRadius: '4px',
|
|
164
|
+
fontSize: '10px',
|
|
165
|
+
border: '1px solid',
|
|
166
|
+
},
|
|
167
|
+
empty: {
|
|
168
|
+
display: 'flex',
|
|
169
|
+
alignItems: 'center',
|
|
170
|
+
gap: '6px',
|
|
171
|
+
fontSize: '11px',
|
|
172
|
+
color: '#22c55e',
|
|
173
|
+
},
|
|
174
|
+
checkIcon: {
|
|
175
|
+
fontWeight: 600,
|
|
176
|
+
},
|
|
177
|
+
list: {
|
|
178
|
+
display: 'flex',
|
|
179
|
+
flexDirection: 'column',
|
|
180
|
+
gap: '6px',
|
|
181
|
+
},
|
|
182
|
+
issue: {
|
|
183
|
+
display: 'flex',
|
|
184
|
+
alignItems: 'flex-start',
|
|
185
|
+
gap: '8px',
|
|
186
|
+
padding: '8px',
|
|
187
|
+
border: '1px solid',
|
|
188
|
+
borderRadius: '4px',
|
|
189
|
+
cursor: 'pointer',
|
|
190
|
+
textAlign: 'left',
|
|
191
|
+
width: '100%',
|
|
192
|
+
transition: 'opacity 0.15s ease-out',
|
|
193
|
+
},
|
|
194
|
+
icon: {
|
|
195
|
+
fontSize: '12px',
|
|
196
|
+
fontWeight: 600,
|
|
197
|
+
flexShrink: 0,
|
|
198
|
+
marginTop: '1px',
|
|
199
|
+
},
|
|
200
|
+
issueContent: {
|
|
201
|
+
display: 'flex',
|
|
202
|
+
flexDirection: 'column',
|
|
203
|
+
gap: '2px',
|
|
204
|
+
overflow: 'hidden',
|
|
205
|
+
},
|
|
206
|
+
issueCode: {
|
|
207
|
+
fontSize: '10px',
|
|
208
|
+
fontWeight: 600,
|
|
209
|
+
color: '#888',
|
|
210
|
+
fontFamily: 'monospace',
|
|
211
|
+
},
|
|
212
|
+
issueMessage: {
|
|
213
|
+
fontSize: '11px',
|
|
214
|
+
color: '#fff',
|
|
215
|
+
lineHeight: 1.4,
|
|
216
|
+
},
|
|
217
|
+
issueSuggestion: {
|
|
218
|
+
fontSize: '10px',
|
|
219
|
+
color: '#888',
|
|
220
|
+
fontStyle: 'italic',
|
|
221
|
+
},
|
|
222
|
+
more: {
|
|
223
|
+
marginTop: '8px',
|
|
224
|
+
fontSize: '10px',
|
|
225
|
+
color: '#666',
|
|
226
|
+
textAlign: 'center',
|
|
227
|
+
},
|
|
228
|
+
}
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lens Panel Component
|
|
3
|
+
*
|
|
4
|
+
* Panel for address impersonation controls.
|
|
5
|
+
* FR-005 fix: All hooks called unconditionally at top level.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState, useCallback } from 'react'
|
|
9
|
+
import { useHud } from '../providers/HudProvider'
|
|
10
|
+
import { DEFAULT_LENS_STATE } from '../types'
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Props for LensPanel
|
|
14
|
+
*/
|
|
15
|
+
export interface LensPanelProps {
|
|
16
|
+
/** Custom class name */
|
|
17
|
+
className?: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Lens panel for address impersonation
|
|
22
|
+
*/
|
|
23
|
+
export function LensPanel({ className = '' }: LensPanelProps) {
|
|
24
|
+
// FR-005 fix: All hooks called unconditionally at top level
|
|
25
|
+
const { lensService, activePanel } = useHud()
|
|
26
|
+
const [inputAddress, setInputAddress] = useState('')
|
|
27
|
+
const [inputLabel, setInputLabel] = useState('')
|
|
28
|
+
|
|
29
|
+
// Get state from service or use default
|
|
30
|
+
const state = lensService?.getState() ?? DEFAULT_LENS_STATE
|
|
31
|
+
const isImpersonating = state.enabled && state.impersonatedAddress !== null
|
|
32
|
+
|
|
33
|
+
// Handle impersonation
|
|
34
|
+
const handleImpersonate = useCallback(() => {
|
|
35
|
+
if (!lensService || !inputAddress) return
|
|
36
|
+
|
|
37
|
+
// Basic validation
|
|
38
|
+
if (!/^0x[a-fA-F0-9]{40}$/.test(inputAddress)) {
|
|
39
|
+
alert('Invalid address format')
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
lensService.setImpersonatedAddress(inputAddress as `0x${string}`)
|
|
44
|
+
setInputAddress('')
|
|
45
|
+
}, [lensService, inputAddress])
|
|
46
|
+
|
|
47
|
+
// Handle save address
|
|
48
|
+
const handleSaveAddress = useCallback(() => {
|
|
49
|
+
if (!lensService || !inputAddress || !inputLabel) return
|
|
50
|
+
|
|
51
|
+
lensService.saveAddress({
|
|
52
|
+
address: inputAddress as `0x${string}`,
|
|
53
|
+
label: inputLabel,
|
|
54
|
+
})
|
|
55
|
+
setInputAddress('')
|
|
56
|
+
setInputLabel('')
|
|
57
|
+
}, [lensService, inputAddress, inputLabel])
|
|
58
|
+
|
|
59
|
+
// Handle stop impersonation
|
|
60
|
+
const handleStopImpersonation = useCallback(() => {
|
|
61
|
+
if (!lensService) return
|
|
62
|
+
lensService.clearImpersonation()
|
|
63
|
+
}, [lensService])
|
|
64
|
+
|
|
65
|
+
// Don't render if not the active panel
|
|
66
|
+
if (activePanel !== 'lens') return null
|
|
67
|
+
|
|
68
|
+
// Show message if lens service not available
|
|
69
|
+
if (!lensService) {
|
|
70
|
+
return (
|
|
71
|
+
<div className={className} style={{ color: '#666' }}>
|
|
72
|
+
<p>Lens service not available.</p>
|
|
73
|
+
<p style={{ fontSize: '10px', marginTop: '8px' }}>
|
|
74
|
+
Install @sigil/lens to enable address impersonation.
|
|
75
|
+
</p>
|
|
76
|
+
</div>
|
|
77
|
+
)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<div className={className}>
|
|
82
|
+
{/* Current Status */}
|
|
83
|
+
<div style={{ marginBottom: '16px' }}>
|
|
84
|
+
<div
|
|
85
|
+
style={{
|
|
86
|
+
display: 'flex',
|
|
87
|
+
alignItems: 'center',
|
|
88
|
+
gap: '8px',
|
|
89
|
+
marginBottom: '8px',
|
|
90
|
+
}}
|
|
91
|
+
>
|
|
92
|
+
<div
|
|
93
|
+
style={{
|
|
94
|
+
width: '8px',
|
|
95
|
+
height: '8px',
|
|
96
|
+
borderRadius: '50%',
|
|
97
|
+
backgroundColor: isImpersonating ? '#10b981' : '#666',
|
|
98
|
+
}}
|
|
99
|
+
/>
|
|
100
|
+
<span style={{ color: isImpersonating ? '#10b981' : '#888' }}>
|
|
101
|
+
{isImpersonating ? 'Impersonating' : 'Not impersonating'}
|
|
102
|
+
</span>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{isImpersonating && (
|
|
106
|
+
<div style={{ marginLeft: '16px' }}>
|
|
107
|
+
<code
|
|
108
|
+
style={{
|
|
109
|
+
fontSize: '11px',
|
|
110
|
+
color: '#10b981',
|
|
111
|
+
wordBreak: 'break-all',
|
|
112
|
+
}}
|
|
113
|
+
>
|
|
114
|
+
{state.impersonatedAddress}
|
|
115
|
+
</code>
|
|
116
|
+
<button
|
|
117
|
+
onClick={handleStopImpersonation}
|
|
118
|
+
style={{
|
|
119
|
+
display: 'block',
|
|
120
|
+
marginTop: '8px',
|
|
121
|
+
padding: '4px 8px',
|
|
122
|
+
backgroundColor: 'rgba(239, 68, 68, 0.2)',
|
|
123
|
+
border: '1px solid rgba(239, 68, 68, 0.3)',
|
|
124
|
+
borderRadius: '4px',
|
|
125
|
+
color: '#ef4444',
|
|
126
|
+
fontSize: '10px',
|
|
127
|
+
cursor: 'pointer',
|
|
128
|
+
}}
|
|
129
|
+
>
|
|
130
|
+
Stop Impersonation
|
|
131
|
+
</button>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
|
|
135
|
+
{state.realAddress && (
|
|
136
|
+
<div style={{ marginTop: '8px', color: '#666', fontSize: '10px' }}>
|
|
137
|
+
Real: {state.realAddress.slice(0, 6)}...{state.realAddress.slice(-4)}
|
|
138
|
+
</div>
|
|
139
|
+
)}
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
{/* Impersonate Address */}
|
|
143
|
+
<div style={{ marginBottom: '16px' }}>
|
|
144
|
+
<label
|
|
145
|
+
style={{
|
|
146
|
+
display: 'block',
|
|
147
|
+
color: '#888',
|
|
148
|
+
fontSize: '10px',
|
|
149
|
+
marginBottom: '4px',
|
|
150
|
+
}}
|
|
151
|
+
>
|
|
152
|
+
Impersonate Address
|
|
153
|
+
</label>
|
|
154
|
+
<input
|
|
155
|
+
type="text"
|
|
156
|
+
value={inputAddress}
|
|
157
|
+
onChange={(e) => setInputAddress(e.target.value)}
|
|
158
|
+
placeholder="0x..."
|
|
159
|
+
style={{
|
|
160
|
+
width: '100%',
|
|
161
|
+
padding: '8px',
|
|
162
|
+
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
|
163
|
+
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
164
|
+
borderRadius: '4px',
|
|
165
|
+
color: '#fff',
|
|
166
|
+
fontSize: '11px',
|
|
167
|
+
fontFamily: 'ui-monospace, monospace',
|
|
168
|
+
}}
|
|
169
|
+
/>
|
|
170
|
+
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
|
171
|
+
<button
|
|
172
|
+
onClick={handleImpersonate}
|
|
173
|
+
disabled={!inputAddress}
|
|
174
|
+
style={{
|
|
175
|
+
flex: 1,
|
|
176
|
+
padding: '6px 12px',
|
|
177
|
+
backgroundColor: inputAddress
|
|
178
|
+
? 'rgba(16, 185, 129, 0.2)'
|
|
179
|
+
: 'rgba(255, 255, 255, 0.05)',
|
|
180
|
+
border: '1px solid rgba(16, 185, 129, 0.3)',
|
|
181
|
+
borderRadius: '4px',
|
|
182
|
+
color: inputAddress ? '#10b981' : '#666',
|
|
183
|
+
fontSize: '11px',
|
|
184
|
+
cursor: inputAddress ? 'pointer' : 'not-allowed',
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
Impersonate
|
|
188
|
+
</button>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
{/* Save Address */}
|
|
193
|
+
<div style={{ marginBottom: '16px' }}>
|
|
194
|
+
<label
|
|
195
|
+
style={{
|
|
196
|
+
display: 'block',
|
|
197
|
+
color: '#888',
|
|
198
|
+
fontSize: '10px',
|
|
199
|
+
marginBottom: '4px',
|
|
200
|
+
}}
|
|
201
|
+
>
|
|
202
|
+
Save with Label
|
|
203
|
+
</label>
|
|
204
|
+
<input
|
|
205
|
+
type="text"
|
|
206
|
+
value={inputLabel}
|
|
207
|
+
onChange={(e) => setInputLabel(e.target.value)}
|
|
208
|
+
placeholder="Label (e.g., Whale)"
|
|
209
|
+
style={{
|
|
210
|
+
width: '100%',
|
|
211
|
+
padding: '8px',
|
|
212
|
+
backgroundColor: 'rgba(255, 255, 255, 0.05)',
|
|
213
|
+
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
214
|
+
borderRadius: '4px',
|
|
215
|
+
color: '#fff',
|
|
216
|
+
fontSize: '11px',
|
|
217
|
+
marginBottom: '8px',
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
<button
|
|
221
|
+
onClick={handleSaveAddress}
|
|
222
|
+
disabled={!inputAddress || !inputLabel}
|
|
223
|
+
style={{
|
|
224
|
+
width: '100%',
|
|
225
|
+
padding: '6px 12px',
|
|
226
|
+
backgroundColor:
|
|
227
|
+
inputAddress && inputLabel
|
|
228
|
+
? 'rgba(59, 130, 246, 0.2)'
|
|
229
|
+
: 'rgba(255, 255, 255, 0.05)',
|
|
230
|
+
border: '1px solid rgba(59, 130, 246, 0.3)',
|
|
231
|
+
borderRadius: '4px',
|
|
232
|
+
color: inputAddress && inputLabel ? '#3b82f6' : '#666',
|
|
233
|
+
fontSize: '11px',
|
|
234
|
+
cursor: inputAddress && inputLabel ? 'pointer' : 'not-allowed',
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
Save Address
|
|
238
|
+
</button>
|
|
239
|
+
</div>
|
|
240
|
+
|
|
241
|
+
{/* Saved Addresses */}
|
|
242
|
+
{state.savedAddresses.length > 0 && (
|
|
243
|
+
<div>
|
|
244
|
+
<label
|
|
245
|
+
style={{
|
|
246
|
+
display: 'block',
|
|
247
|
+
color: '#888',
|
|
248
|
+
fontSize: '10px',
|
|
249
|
+
marginBottom: '8px',
|
|
250
|
+
}}
|
|
251
|
+
>
|
|
252
|
+
Saved Addresses
|
|
253
|
+
</label>
|
|
254
|
+
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
|
255
|
+
{state.savedAddresses.map((saved) => (
|
|
256
|
+
<button
|
|
257
|
+
key={saved.address}
|
|
258
|
+
onClick={() =>
|
|
259
|
+
lensService?.setImpersonatedAddress(saved.address)
|
|
260
|
+
}
|
|
261
|
+
style={{
|
|
262
|
+
display: 'flex',
|
|
263
|
+
justifyContent: 'space-between',
|
|
264
|
+
alignItems: 'center',
|
|
265
|
+
padding: '8px',
|
|
266
|
+
backgroundColor: 'rgba(255, 255, 255, 0.02)',
|
|
267
|
+
border: '1px solid rgba(255, 255, 255, 0.05)',
|
|
268
|
+
borderRadius: '4px',
|
|
269
|
+
cursor: 'pointer',
|
|
270
|
+
textAlign: 'left',
|
|
271
|
+
}}
|
|
272
|
+
>
|
|
273
|
+
<span style={{ color: '#fff', fontSize: '11px' }}>
|
|
274
|
+
{saved.label}
|
|
275
|
+
</span>
|
|
276
|
+
<code style={{ color: '#666', fontSize: '10px' }}>
|
|
277
|
+
{saved.address.slice(0, 6)}...{saved.address.slice(-4)}
|
|
278
|
+
</code>
|
|
279
|
+
</button>
|
|
280
|
+
))}
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
</div>
|
|
285
|
+
)
|
|
286
|
+
}
|