@monygroupcorp/micro-web3 0.1.3 → 1.2.1

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/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@monygroupcorp/micro-web3",
3
- "version": "0.1.3",
3
+ "version": "1.2.1",
4
4
  "description": "A lean, reusable Web3 toolkit with components for wallet connection, IPFS, and common Web3 UI patterns.",
5
- "main": "dist/micro-web3.cjs.js",
5
+ "main": "dist/micro-web3.cjs",
6
6
  "module": "dist/micro-web3.esm.js",
7
7
  "type": "module",
8
8
  "scripts": {
package/rollup.config.cjs CHANGED
@@ -7,7 +7,7 @@ module.exports = [
7
7
  input: 'src/index.js',
8
8
  output: [
9
9
  {
10
- file: 'dist/micro-web3.cjs.js',
10
+ file: 'dist/micro-web3.cjs',
11
11
  format: 'cjs',
12
12
  sourcemap: true,
13
13
  },
@@ -1,5 +1,6 @@
1
1
  import { Component, eventBus } from '@monygroupcorp/microact';
2
2
  import WalletModal from '../Wallet/WalletModal.js';
3
+ import { SettingsModal } from '../SettingsModal/SettingsModal.js';
3
4
  import { ethers } from 'ethers';
4
5
 
5
6
  /**
@@ -12,6 +13,7 @@ export class FloatingWalletButton extends Component {
12
13
  super();
13
14
  this.walletService = props.walletService;
14
15
  this.walletModal = null;
16
+ this.settingsModal = null;
15
17
  this.state = {
16
18
  walletConnected: false,
17
19
  address: null,
@@ -47,30 +49,31 @@ export class FloatingWalletButton extends Component {
47
49
  let isConnected = this.walletService.isConnected();
48
50
  let address = this.walletService.getAddress();
49
51
 
50
- // If not connected, try to auto-reconnect to last used wallet
52
+ // If not connected, check if window.ethereum already has connected accounts
51
53
  if (!isConnected && typeof window.ethereum !== 'undefined') {
52
54
  try {
53
- const lastWallet = localStorage.getItem('ms2fun_lastWallet');
54
-
55
- if (lastWallet) {
56
- // Check if that wallet has accounts (without prompting)
57
- const accounts = await window.ethereum.request({ method: 'eth_accounts' });
58
- const hasAccounts = accounts && accounts.length > 0;
59
-
60
- // Only try to reconnect if the last wallet has accounts
61
- if (hasAccounts) {
62
- try {
63
- await this.walletService.selectWallet(lastWallet);
64
- await this.walletService.connect();
65
- isConnected = this.walletService.isConnected();
66
- address = this.walletService.getAddress();
67
-
68
- if (isConnected) {
69
- console.log('[FloatingWalletButton] Auto-reconnected to', lastWallet);
70
- }
71
- } catch (connectError) {
72
- console.log('[FloatingWalletButton] Auto-reconnect not possible');
55
+ // eth_accounts returns already-authorized accounts without prompting
56
+ const accounts = await window.ethereum.request({ method: 'eth_accounts' });
57
+ const hasAccounts = accounts && accounts.length > 0;
58
+
59
+ if (hasAccounts) {
60
+ // Detect which wallet is active
61
+ const walletType = this._detectWalletType();
62
+ console.log('[FloatingWalletButton] Detected existing connection:', walletType, accounts[0]);
63
+
64
+ try {
65
+ await this.walletService.selectWallet(walletType);
66
+ await this.walletService.connect();
67
+ isConnected = this.walletService.isConnected();
68
+ address = this.walletService.getAddress();
69
+
70
+ if (isConnected) {
71
+ console.log('[FloatingWalletButton] Auto-connected to existing session:', walletType);
72
+ // Update lastWallet for future sessions
73
+ localStorage.setItem('ms2fun_lastWallet', walletType);
73
74
  }
75
+ } catch (connectError) {
76
+ console.log('[FloatingWalletButton] Could not auto-connect:', connectError.message);
74
77
  }
75
78
  }
76
79
  } catch (error) {
@@ -89,6 +92,21 @@ export class FloatingWalletButton extends Component {
89
92
  }
90
93
  }
91
94
 
95
+ /**
96
+ * Detect which wallet type is currently active based on provider flags
97
+ */
98
+ _detectWalletType() {
99
+ if (typeof window.ethereum === 'undefined') return 'metamask';
100
+
101
+ // Check specific wallet flags (order matters - more specific first)
102
+ if (window.ethereum.isRabby) return 'rabby';
103
+ if (window.ethereum.isRainbow) return 'rainbow';
104
+ if (window.phantom?.ethereum) return 'phantom';
105
+
106
+ // Default to metamask for any EIP-1193 provider
107
+ return 'metamask';
108
+ }
109
+
92
110
  /**
93
111
  * Load wallet info (balance, EXEC tokens, vault benefactor status)
94
112
  */
@@ -643,6 +661,7 @@ export class FloatingWalletButton extends Component {
643
661
  this.handleMenuItemClick(route);
644
662
  } else if (action === 'settings') {
645
663
  this.setState({ menuOpen: false });
664
+ this.openSettings();
646
665
  }
647
666
  });
648
667
  });
@@ -663,12 +682,25 @@ export class FloatingWalletButton extends Component {
663
682
  }
664
683
  }
665
684
 
685
+ openSettings() {
686
+ if (!this.settingsModal) {
687
+ this.settingsModal = new SettingsModal({ eventBus: eventBus });
688
+ }
689
+ this.settingsModal.show();
690
+ }
691
+
666
692
  onUnmount() {
667
693
  // Clean up wallet modal
668
694
  if (this.walletModal) {
669
695
  this.walletModal.hide();
670
696
  this.walletModal = null;
671
697
  }
698
+
699
+ // Clean up settings modal
700
+ if (this.settingsModal) {
701
+ this.settingsModal.hide();
702
+ this.settingsModal = null;
703
+ }
672
704
  }
673
705
 
674
706
  escapeHtml(text) {
@@ -0,0 +1,371 @@
1
+ // src/components/SettingsModal/SettingsModal.js
2
+
3
+ import { IndexerSettings } from '../../storage/index.js';
4
+
5
+ /**
6
+ * SettingsModal - Modal for user settings including indexer storage controls.
7
+ *
8
+ * @example
9
+ * const modal = new SettingsModal({ eventBus });
10
+ * modal.show();
11
+ */
12
+ class SettingsModal {
13
+ constructor({ eventBus }) {
14
+ this.eventBus = eventBus;
15
+ this.element = null;
16
+ this.overlay = null;
17
+ this.isVisible = false;
18
+ this.state = {
19
+ storageEnabled: true,
20
+ storageUsed: 0,
21
+ storageQuota: 0,
22
+ clearing: false
23
+ };
24
+ }
25
+
26
+ async show() {
27
+ if (this.isVisible) return;
28
+
29
+ // Load current settings
30
+ const settings = IndexerSettings.get();
31
+ const estimate = await IndexerSettings.getStorageEstimate();
32
+
33
+ this.state = {
34
+ storageEnabled: settings.storageEnabled,
35
+ storageUsed: estimate.used || 0,
36
+ storageQuota: estimate.quota || 0,
37
+ clearing: false
38
+ };
39
+
40
+ this._createModal();
41
+ this.isVisible = true;
42
+ }
43
+
44
+ hide() {
45
+ if (!this.isVisible) return;
46
+
47
+ if (this.overlay && this.overlay.parentNode) {
48
+ this.overlay.parentNode.removeChild(this.overlay);
49
+ }
50
+ this.overlay = null;
51
+ this.element = null;
52
+ this.isVisible = false;
53
+ }
54
+
55
+ _createModal() {
56
+ // Inject styles if needed
57
+ SettingsModal.injectStyles();
58
+
59
+ // Create overlay
60
+ this.overlay = document.createElement('div');
61
+ this.overlay.className = 'mw3-settings-overlay';
62
+ this.overlay.addEventListener('click', (e) => {
63
+ if (e.target === this.overlay) this.hide();
64
+ });
65
+
66
+ // Create modal
67
+ this.element = document.createElement('div');
68
+ this.element.className = 'mw3-settings-modal';
69
+ this.element.innerHTML = this._render();
70
+
71
+ this.overlay.appendChild(this.element);
72
+ document.body.appendChild(this.overlay);
73
+
74
+ this._setupEventListeners();
75
+ }
76
+
77
+ _render() {
78
+ const { storageEnabled, storageUsed, storageQuota, clearing } = this.state;
79
+ const usedMB = (storageUsed / (1024 * 1024)).toFixed(1);
80
+ const quotaMB = (storageQuota / (1024 * 1024)).toFixed(0);
81
+
82
+ return `
83
+ <div class="mw3-settings-header">
84
+ <h2>Settings</h2>
85
+ <button class="mw3-settings-close" data-action="close">&times;</button>
86
+ </div>
87
+
88
+ <div class="mw3-settings-content">
89
+ <div class="mw3-settings-section">
90
+ <h3>Data Storage</h3>
91
+ <p class="mw3-settings-description">
92
+ Event data is cached locally to improve performance. You can disable this to save space.
93
+ </p>
94
+
95
+ <div class="mw3-settings-row">
96
+ <label class="mw3-settings-toggle">
97
+ <input type="checkbox" ${storageEnabled ? 'checked' : ''} data-setting="storageEnabled">
98
+ <span class="mw3-toggle-slider"></span>
99
+ <span class="mw3-toggle-label">Enable local event storage</span>
100
+ </label>
101
+ </div>
102
+
103
+ <div class="mw3-settings-info">
104
+ <span>Storage used: ${usedMB} MB${quotaMB > 0 ? ` / ${quotaMB} MB` : ''}</span>
105
+ </div>
106
+
107
+ <div class="mw3-settings-row">
108
+ <button class="mw3-settings-btn mw3-settings-btn--danger" data-action="clearData" ${clearing ? 'disabled' : ''}>
109
+ ${clearing ? 'Clearing...' : 'Clear All Cached Data'}
110
+ </button>
111
+ </div>
112
+
113
+ <p class="mw3-settings-note">
114
+ Disabling storage means event history will need to be re-fetched each session.
115
+ </p>
116
+ </div>
117
+ </div>
118
+
119
+ <div class="mw3-settings-footer">
120
+ <button class="mw3-settings-btn mw3-settings-btn--primary" data-action="close">Done</button>
121
+ </div>
122
+ `;
123
+ }
124
+
125
+ _setupEventListeners() {
126
+ // Close button
127
+ this.element.querySelectorAll('[data-action="close"]').forEach(btn => {
128
+ btn.addEventListener('click', () => this.hide());
129
+ });
130
+
131
+ // Storage toggle
132
+ const toggle = this.element.querySelector('[data-setting="storageEnabled"]');
133
+ if (toggle) {
134
+ toggle.addEventListener('change', (e) => {
135
+ const enabled = e.target.checked;
136
+ IndexerSettings.set({ storageEnabled: enabled });
137
+ this.state.storageEnabled = enabled;
138
+
139
+ // Emit event so indexer can react
140
+ this.eventBus.emit('settings:storageChanged', { enabled });
141
+ });
142
+ }
143
+
144
+ // Clear data button
145
+ const clearBtn = this.element.querySelector('[data-action="clearData"]');
146
+ if (clearBtn) {
147
+ clearBtn.addEventListener('click', async () => {
148
+ if (this.state.clearing) return;
149
+
150
+ this.state.clearing = true;
151
+ this._updateContent();
152
+
153
+ try {
154
+ await IndexerSettings.clearAllData();
155
+
156
+ // Update storage estimate
157
+ const estimate = await IndexerSettings.getStorageEstimate();
158
+ this.state.storageUsed = estimate.used || 0;
159
+ this.state.storageQuota = estimate.quota || 0;
160
+
161
+ // Emit event so indexer knows to resync
162
+ this.eventBus.emit('settings:dataCleared', {});
163
+ } catch (error) {
164
+ console.error('[SettingsModal] Failed to clear data:', error);
165
+ }
166
+
167
+ this.state.clearing = false;
168
+ this._updateContent();
169
+ });
170
+ }
171
+
172
+ // ESC key to close
173
+ this._escHandler = (e) => {
174
+ if (e.key === 'Escape') this.hide();
175
+ };
176
+ document.addEventListener('keydown', this._escHandler);
177
+ }
178
+
179
+ _updateContent() {
180
+ if (this.element) {
181
+ this.element.innerHTML = this._render();
182
+ this._setupEventListeners();
183
+ }
184
+ }
185
+
186
+ static injectStyles() {
187
+ if (document.getElementById('mw3-settings-styles')) return;
188
+
189
+ const style = document.createElement('style');
190
+ style.id = 'mw3-settings-styles';
191
+ style.textContent = `
192
+ .mw3-settings-overlay {
193
+ position: fixed;
194
+ top: 0;
195
+ left: 0;
196
+ right: 0;
197
+ bottom: 0;
198
+ background: rgba(0, 0, 0, 0.7);
199
+ display: flex;
200
+ align-items: center;
201
+ justify-content: center;
202
+ z-index: 10000;
203
+ backdrop-filter: blur(4px);
204
+ }
205
+
206
+ .mw3-settings-modal {
207
+ background: linear-gradient(135deg, #1a1a2e, #16213e);
208
+ border: 1px solid rgba(218, 165, 32, 0.3);
209
+ border-radius: 12px;
210
+ width: 90%;
211
+ max-width: 400px;
212
+ color: #fff;
213
+ font-family: 'Lato', system-ui, sans-serif;
214
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.5);
215
+ }
216
+
217
+ .mw3-settings-header {
218
+ display: flex;
219
+ align-items: center;
220
+ justify-content: space-between;
221
+ padding: 1rem 1.25rem;
222
+ border-bottom: 1px solid rgba(218, 165, 32, 0.2);
223
+ }
224
+
225
+ .mw3-settings-header h2 {
226
+ margin: 0;
227
+ font-size: 1.125rem;
228
+ font-weight: 600;
229
+ color: rgba(218, 165, 32, 0.95);
230
+ }
231
+
232
+ .mw3-settings-close {
233
+ background: none;
234
+ border: none;
235
+ color: rgba(255, 255, 255, 0.6);
236
+ font-size: 1.5rem;
237
+ cursor: pointer;
238
+ padding: 0;
239
+ line-height: 1;
240
+ }
241
+
242
+ .mw3-settings-close:hover {
243
+ color: #fff;
244
+ }
245
+
246
+ .mw3-settings-content {
247
+ padding: 1.25rem;
248
+ }
249
+
250
+ .mw3-settings-section h3 {
251
+ margin: 0 0 0.5rem 0;
252
+ font-size: 0.9rem;
253
+ font-weight: 600;
254
+ color: rgba(255, 255, 255, 0.9);
255
+ }
256
+
257
+ .mw3-settings-description {
258
+ margin: 0 0 1rem 0;
259
+ font-size: 0.8rem;
260
+ color: rgba(255, 255, 255, 0.6);
261
+ line-height: 1.4;
262
+ }
263
+
264
+ .mw3-settings-row {
265
+ margin-bottom: 1rem;
266
+ }
267
+
268
+ .mw3-settings-toggle {
269
+ display: flex;
270
+ align-items: center;
271
+ gap: 0.75rem;
272
+ cursor: pointer;
273
+ }
274
+
275
+ .mw3-settings-toggle input {
276
+ display: none;
277
+ }
278
+
279
+ .mw3-toggle-slider {
280
+ width: 44px;
281
+ height: 24px;
282
+ background: rgba(255, 255, 255, 0.2);
283
+ border-radius: 12px;
284
+ position: relative;
285
+ transition: background 0.2s;
286
+ }
287
+
288
+ .mw3-toggle-slider::after {
289
+ content: '';
290
+ position: absolute;
291
+ top: 2px;
292
+ left: 2px;
293
+ width: 20px;
294
+ height: 20px;
295
+ background: #fff;
296
+ border-radius: 50%;
297
+ transition: transform 0.2s;
298
+ }
299
+
300
+ .mw3-settings-toggle input:checked + .mw3-toggle-slider {
301
+ background: rgba(218, 165, 32, 0.8);
302
+ }
303
+
304
+ .mw3-settings-toggle input:checked + .mw3-toggle-slider::after {
305
+ transform: translateX(20px);
306
+ }
307
+
308
+ .mw3-toggle-label {
309
+ font-size: 0.875rem;
310
+ color: rgba(255, 255, 255, 0.9);
311
+ }
312
+
313
+ .mw3-settings-info {
314
+ font-size: 0.75rem;
315
+ color: rgba(255, 255, 255, 0.5);
316
+ margin-bottom: 1rem;
317
+ }
318
+
319
+ .mw3-settings-note {
320
+ font-size: 0.75rem;
321
+ color: rgba(255, 255, 255, 0.4);
322
+ margin: 0;
323
+ font-style: italic;
324
+ }
325
+
326
+ .mw3-settings-btn {
327
+ padding: 0.625rem 1rem;
328
+ border-radius: 6px;
329
+ font-size: 0.875rem;
330
+ font-weight: 500;
331
+ cursor: pointer;
332
+ border: none;
333
+ transition: all 0.2s;
334
+ }
335
+
336
+ .mw3-settings-btn--primary {
337
+ background: rgba(218, 165, 32, 0.9);
338
+ color: #1a1a2e;
339
+ }
340
+
341
+ .mw3-settings-btn--primary:hover {
342
+ background: rgba(218, 165, 32, 1);
343
+ }
344
+
345
+ .mw3-settings-btn--danger {
346
+ background: rgba(255, 99, 71, 0.2);
347
+ color: rgba(255, 99, 71, 0.9);
348
+ border: 1px solid rgba(255, 99, 71, 0.3);
349
+ }
350
+
351
+ .mw3-settings-btn--danger:hover {
352
+ background: rgba(255, 99, 71, 0.3);
353
+ }
354
+
355
+ .mw3-settings-btn:disabled {
356
+ opacity: 0.5;
357
+ cursor: not-allowed;
358
+ }
359
+
360
+ .mw3-settings-footer {
361
+ padding: 1rem 1.25rem;
362
+ border-top: 1px solid rgba(218, 165, 32, 0.2);
363
+ display: flex;
364
+ justify-content: flex-end;
365
+ }
366
+ `;
367
+ document.head.appendChild(style);
368
+ }
369
+ }
370
+
371
+ export { SettingsModal };