chore: added popover 95507083
Steve · 2025-09-20 00:49 1 file(s) · +205 −20
src/components/connect-wallet.js +205 −20
8 8
		this.loading = false;
9 9
		this.chainId = "0x1";
10 10
		this.currentChainId = null;
11 +
		this.showPopover = false;
12 +
		this.balance = "0";
13 +
		this.copySuccess = false;
11 14
	}
12 15
13 16
	static get observedAttributes() {
54 57
55 58
				this.connected = true;
56 59
57 -
				await this.fetchEnsData();
60 +
				await Promise.all([this.fetchEnsData(), this.fetchBalance()]);
58 61
59 62
				this.loading = false;
60 63
				this.render();
191 194
		};
192 195
		return chainNames[chainId] || `Chain ${chainId}`;
193 196
	}
197 +
198 +
	getChainSymbol(chainId) {
199 +
		const chainSymbols = {
200 +
			"0x1": "ETH",
201 +
			"0x89": "MATIC",
202 +
			"0xa": "ETH",
203 +
			"0xa4b1": "ETH",
204 +
			"0x2105": "ETH",
205 +
			// Add more as needed
206 +
		};
207 +
		return chainSymbols[chainId] || "ETH";
208 +
	}
194 209
	renderProfile() {
210 +
		const profileContainer = document.createElement("div");
211 +
		profileContainer.className = "profile-container";
212 +
195 213
		const profileDiv = document.createElement("div");
196 214
		profileDiv.className = "profile";
197 215
198 216
		const avatar = this.ensData?.avatar_small;
199 217
		const displayName = this.getDisplayName();
200 -
		const hasEns = this.ensData?.ens_primary;
201 -
		const addressToShow = this.truncateAddress(this.address);
218 +
		const chainSymbol = this.getChainSymbol(this.currentChainId);
202 219
203 220
		// Create avatar element
204 221
		let avatarElement = "";
212 229
			${avatarElement}
213 230
			<div class="profile-info">
214 231
				<h3>${displayName}</h3>
215 -
				${hasEns ? `<p>${addressToShow}</p>` : ""}
232 +
				<p>${this.balance} ${chainSymbol}</p>
216 233
			</div>
217 234
		`;
218 235
219 -
		profileDiv.addEventListener("click", () => {
220 -
			this.disconnect();
236 +
		profileDiv.addEventListener("click", (e) => {
237 +
			e.stopPropagation();
238 +
			this.togglePopover();
221 239
		});
222 240
223 -
		this.shadowRoot.appendChild(profileDiv);
241 +
		profileContainer.appendChild(profileDiv);
242 +
		this.shadowRoot.appendChild(profileContainer);
243 +
244 +
		// Show popover if it was open before re-render
245 +
		if (this.showPopover) {
246 +
			this.showPopoverElement();
247 +
		}
224 248
	}
225 249
226 250
	async fetchEnsData() {
239 263
		}
240 264
	}
241 265
266 +
	async fetchBalance() {
267 +
		try {
268 +
			const balanceWei = await window.ethereum.request({
269 +
				method: "eth_getBalance",
270 +
				params: [this.address, "latest"],
271 +
			});
272 +
273 +
			// Convert from wei to ether (divide by 10^18)
274 +
			const balanceEth = parseInt(balanceWei, 16) / Math.pow(10, 18);
275 +
			this.balance = balanceEth.toFixed(4);
276 +
		} catch (error) {
277 +
			console.error("Failed to fetch balance", error);
278 +
			this.balance = "0";
279 +
		}
280 +
	}
281 +
282 +
	togglePopover() {
283 +
		this.showPopover = !this.showPopover;
284 +
		if (this.showPopover) {
285 +
			this.showPopoverElement();
286 +
		} else {
287 +
			this.hidePopoverElement();
288 +
		}
289 +
	}
290 +
291 +
	hidePopover() {
292 +
		if (this.showPopover) {
293 +
			this.showPopover = false;
294 +
			this.hidePopoverElement();
295 +
		}
296 +
	}
297 +
298 +
	showPopoverElement() {
299 +
		const profileContainer =
300 +
			this.shadowRoot.querySelector(".profile-container");
301 +
		if (!profileContainer) return;
302 +
303 +
		// Remove existing popover if any
304 +
		const existingPopover = profileContainer.querySelector(".popover");
305 +
		if (existingPopover) {
306 +
			existingPopover.remove();
307 +
		}
308 +
309 +
		const popover = document.createElement("div");
310 +
		popover.className = "popover";
311 +
312 +
		const copyIcon = this.copySuccess
313 +
			? `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M11.4669 3.72684C11.7558 3.91574 11.8369 4.30308 11.648 4.59198L7.39799 11.092C7.29783 11.2452 7.13556 11.3467 6.95402 11.3699C6.77247 11.3931 6.58989 11.3355 6.45446 11.2124L3.70446 8.71241C3.44905 8.48022 3.43023 8.08494 3.66242 7.82953C3.89461 7.57412 4.28989 7.55529 4.5453 7.78749L6.75292 9.79441L10.6018 3.90792C10.7907 3.61902 11.178 3.53795 11.4669 3.72684Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`
314 +
			: `<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M5 2V1H10V2H5ZM4.75 0C4.33579 0 4 0.335786 4 0.75V1H3.5C2.67157 1 2 1.67157 2 2.5V12.5C2 13.3284 2.67157 14 3.5 14H7V13H3.5C3.22386 13 3 12.7761 3 12.5V2.5C3 2.22386 3.22386 2 3.5 2H4V2.25C4 2.66421 4.33579 3 4.75 3H10.25C10.6642 3 11 2.66421 11 2.25V2H11.5C11.7761 2 12 2.22386 12 2.5V7H13V2.5C13 1.67157 12.3284 1 11.5 1H11V0.75C11 0.335786 10.6642 0 10.25 0H4.75ZM9 8.5C9 8.77614 8.77614 9 8.5 9C8.22386 9 8 8.77614 8 8.5C8 8.22386 8.22386 8 8.5 8C8.77614 8 9 8.22386 9 8.5ZM10.5 9C10.7761 9 11 8.77614 11 8.5C11 8.22386 10.7761 8 10.5 8C10.2239 8 10 8.22386 10 8.5C10 8.77614 10.2239 9 10.5 9ZM13 8.5C13 8.77614 12.7761 9 12.5 9C12.2239 9 12 8.77614 12 8.5C12 8.22386 12.2239 8 12.5 8C12.7761 8 13 8.22386 13 8.5ZM14.5 9C14.7761 9 15 8.77614 15 8.5C15 8.22386 14.7761 8 14.5 8C14.2239 8 14 8.22386 14 8.5C14 8.77614 14.2239 9 14.5 9ZM15 10.5C15 10.7761 14.7761 11 14.5 11C14.2239 11 14 10.7761 14 10.5C14 10.2239 14.2239 10 14.5 10C14.7761 10 15 10.2239 15 10.5ZM14.5 13C14.7761 13 15 12.7761 15 12.5C15 12.2239 14.7761 12 14.5 12C14.2239 12 14 12.2239 14 12.5C14 12.7761 14.2239 13 14.5 13ZM14.5 15C14.7761 15 15 14.7761 15 14.5C15 14.2239 14.7761 14 14.5 14C14.2239 14 14 14.2239 14 14.5C14 14.7761 14.2239 15 14.5 15ZM8.5 11C8.77614 11 9 10.7761 9 10.5C9 10.2239 8.77614 10 8.5 10C8.22386 10 8 10.2239 8 10.5C8 10.7761 8.22386 11 8.5 11ZM9 12.5C9 12.7761 8.77614 13 8.5 13C8.22386 13 8 12.7761 8 12.5C8 12.2239 8.22386 12 8.5 12C8.77614 12 9 12.2239 9 12.5ZM8.5 15C8.77614 15 9 14.7761 9 14.5C9 14.2239 8.77614 14 8.5 14C8.22386 14 8 14.2239 8 14.5C8 14.7761 8.22386 15 8.5 15ZM11 14.5C11 14.7761 10.7761 15 10.5 15C10.2239 15 10 14.7761 10 14.5C10 14.2239 10.2239 14 10.5 14C10.7761 14 11 14.2239 11 14.5ZM12.5 15C12.7761 15 13 14.7761 13 14.5C13 14.2239 12.7761 14 12.5 14C12.2239 14 12 14.2239 12 14.5C12 12.7761 12.2239 15 12.5 15Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>`;
315 +
316 +
		const copyText = this.copySuccess ? "Copied!" : "Copy Address";
317 +
		popover.innerHTML = `
318 +
			<button class="popover-button copy-button">
319 +
				<span>${copyIcon}</span>
320 +
				${copyText}
321 +
			</button>
322 +
			<button class="popover-button disconnect-button">
323 +
				<span><svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M3 1C2.44771 1 2 1.44772 2 2V13C2 13.5523 2.44772 14 3 14H10.5C10.7761 14 11 13.7761 11 13.5C11 13.2239 10.7761 13 10.5 13H3V2L10.5 2C10.7761 2 11 1.77614 11 1.5C11 1.22386 10.7761 1 10.5 1H3ZM12.6036 4.89645C12.4083 4.70118 12.0917 4.70118 11.8964 4.89645C11.7012 5.09171 11.7012 5.40829 11.8964 5.60355L13.2929 7H6.5C6.22386 7 6 7.22386 6 7.5C6 7.77614 6.22386 8 6.5 8H13.2929L11.8964 9.39645C11.7012 9.59171 11.7012 9.90829 11.8964 10.1036C12.0917 10.2988 12.4083 10.2988 12.6036 10.1036L14.8536 7.85355C15.0488 7.65829 15.0488 7.34171 14.8536 7.14645L12.6036 4.89645Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg></span>
324 +
				Disconnect
325 +
			</button>
326 +
		`;
327 +
328 +
		popover.querySelector(".copy-button").addEventListener("click", (e) => {
329 +
			e.stopPropagation();
330 +
			this.copyAddress();
331 +
		});
332 +
333 +
		popover
334 +
			.querySelector(".disconnect-button")
335 +
			.addEventListener("click", (e) => {
336 +
				e.stopPropagation();
337 +
				this.disconnect();
338 +
			});
339 +
340 +
		profileContainer.appendChild(popover);
341 +
342 +
		// Add click outside listener to close popover
343 +
		setTimeout(() => {
344 +
			document.addEventListener("click", this.hidePopover.bind(this), {
345 +
				once: true,
346 +
			});
347 +
		}, 0);
348 +
	}
349 +
350 +
	hidePopoverElement() {
351 +
		const profileContainer =
352 +
			this.shadowRoot.querySelector(".profile-container");
353 +
		if (!profileContainer) return;
354 +
355 +
		const popover = profileContainer.querySelector(".popover");
356 +
		if (popover) {
357 +
			popover.remove();
358 +
		}
359 +
	}
360 +
361 +
	async copyAddress() {
362 +
		try {
363 +
			await navigator.clipboard.writeText(this.address);
364 +
			this.copySuccess = true;
365 +
			this.showPopoverElement(); // Re-render popover with checkmark
366 +
367 +
			// Reset after 1 second
368 +
			setTimeout(() => {
369 +
				this.copySuccess = false;
370 +
				this.showPopoverElement(); // Re-render popover back to normal
371 +
			}, 1000);
372 +
		} catch (error) {
373 +
			console.error("Failed to copy address", error);
374 +
		}
375 +
	}
376 +
242 377
	disconnect() {
243 378
		this.connected = false;
244 379
		this.address = "";
245 380
		this.ensData = null;
246 381
		this.currentChainId = null;
382 +
		this.balance = "0";
383 +
		this.showPopover = false;
384 +
		this.copySuccess = false;
247 385
		this.render();
248 386
249 387
		// Dispatch custom event
280 418
					cursor: not-allowed;
281 419
				}
282 420
421 +
				.profile-container {
422 +
					position: relative;
423 +
					display: inline-block;
424 +
				}
425 +
283 426
				.profile {
284 427
					display: flex;
285 428
					align-items: center;
286 -
					gap: 12px;
287 -
					padding: 12px;
429 +
					gap: 8px;
430 +
					padding: 10px 20px;
288 431
					background: var(--bg-color);
289 -
					border-radius: 8px;
432 +
					border-radius: 4px;
433 +
					border: 1px solid rgba(255, 255, 255, 0.1);
290 434
					color: white;
291 -
					min-width: 220px;
435 +
					min-width: auto;
292 436
					transition: background-color 0.3s ease;
437 +
					cursor: pointer;
293 438
				}
294 439
295 440
				.profile:hover {
296 441
					background: var(--bg-hover-color);
297 442
				}
298 443
444 +
				.popover {
445 +
					position: absolute;
446 +
					top: 100%;
447 +
					left: 0;
448 +
					right: 0;
449 +
					background: var(--bg-color);
450 +
					border: 1px solid rgba(255, 255, 255, 0.1);
451 +
					border-radius: 4px;
452 +
					box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
453 +
					z-index: 1000;
454 +
					margin-top: 4px;
455 +
					overflow: hidden;
456 +
				}
457 +
458 +
				.popover-button {
459 +
					display: flex;
460 +
					align-items: center;
461 +
					gap: 8px;
462 +
					width: 100%;
463 +
					padding: 10px 16px;
464 +
					background: var(--bg-color);
465 +
					border: none;
466 +
					color: white;
467 +
					font-size: 14px;
468 +
					cursor: pointer;
469 +
					transition: background-color 0.2s ease;
470 +
				}
471 +
472 +
				.popover-button:hover {
473 +
					background: var(--bg-hover-color);
474 +
				}
475 +
476 +
				.popover-button:not(:last-child) {
477 +
					border-bottom: 1px solid rgba(255, 255, 255, 0.1);
478 +
				}
479 +
480 +
				.popover-button span {
481 +
					font-size: 16px;
482 +
				}
483 +
299 484
				.avatar {
300 -
					width: 40px;
301 -
					height: 40px;
485 +
					width: 24px;
486 +
					height: 24px;
302 487
					border-radius: 50%;
303 488
					object-fit: cover;
304 -
					border: 2px solid rgba(255, 255, 255, 0.2);
489 +
					border: 1px solid rgba(255, 255, 255, 0.2);
305 490
				}
306 491
307 492
				.avatar-placeholder {
308 -
					width: 40px;
309 -
					height: 40px;
493 +
					width: 24px;
494 +
					height: 24px;
310 495
					border-radius: 50%;
311 496
					background: linear-gradient(45deg, #667eea, #764ba2);
312 497
					display: flex;
314 499
					justify-content: center;
315 500
					color: white;
316 501
					font-weight: bold;
317 -
					font-size: 16px;
502 +
					font-size: 12px;
318 503
				}
319 504
320 505
				.profile-info {
323 508
				}
324 509
325 510
				.profile-info h3 {
326 -
					margin: 0 0 4px 0;
327 -
					font-size: 14px;
511 +
					margin: 0 0 2px 0;
512 +
					font-size: 16px;
328 513
					font-weight: 600;
329 514
					white-space: nowrap;
330 515
					overflow: hidden;
333 518
334 519
				.profile-info p {
335 520
					margin: 0;
336 -
					font-size: 12px;
521 +
					font-size: 14px;
337 522
					opacity: 0.8;
338 523
					white-space: nowrap;
339 524
					overflow: hidden;