1590 lines
59 KiB
TypeScript
Executable File

declare global {
interface Window {
initAccount: () => void;
showContractPopup: (contractId: string) => void;
showPairing: () => Promise<void>;
showWallet: () => void;
showData: () => void;
addWalletRow: () => void;
confirmWalletRow: () => void;
cancelWalletRow: () => void;
openAvatarPopup: () => void;
closeAvatarPopup: () => void;
editDeviceName: (cell: HTMLTableCellElement) => void;
showNotifications: (processName: string) => void;
closeNotificationPopup: (event: Event) => void;
markAsRead: (processName: string, messageId: number, element: HTMLElement) => void;
exportRecovery: () => void;
confirmDeleteAccount: () => void;
deleteAccount: () => void;
updateNavbarBanner: (bannerUrl: string) => void;
saveBannerToLocalStorage: (bannerUrl: string) => void;
loadSavedBanner: () => void;
cancelAddRowPairing: () => void;
saveName: (cell: HTMLElement, input: HTMLInputElement) => void;
showProcessNotifications: (processName: string) => void;
handleLogout: () => void;
initializeEventListeners: () => void;
showProcess: () => void;
showProcessCreation: () => void;
showDocumentValidation: () => void;
updateNavbarName: (name: string) => void;
updateNavbarLastName: (lastName: string) => void;
showAlert: (title: string, text?: string, icon?: string) => void;
addRowPairing: () => void;
confirmRowPairing: () => void;
cancelRowPairing: () => void;
deleteRowPairing: (button: HTMLButtonElement) => void;
generateRecoveryWords: () => string[];
exportUserData: () => void;
updateActionButtons: () => void;
showQRCodeModal: (pairingId: string) => void;
}
}
import Swal from 'sweetalert2';
import { STORAGE_KEYS, defaultRows, mockProcessRows, mockNotifications, notificationMessages, mockDataRows, mockContracts, ALLOWED_ROLES } from '../../mocks/mock-account/constAccountMock';
import { Row, WalletRow, DataRow, Notification, Contract, NotificationMessage } from '../../mocks/mock-account/interfacesAccountMock';
import { addressToEmoji } from '../../utils/sp-address.utils';
import { getCorrectDOM } from '../../utils/document.utils';
import accountStyle from '../../../public/style/account.css?inline';
import Services from '../../services/service';
import { getProcessCreation } from './process-creation';
import { getDocumentValidation } from './document-validation';
import { createProcessTab } from './process';
let isAddingRow = false;
let currentRow: HTMLTableRowElement | null = null;
let currentMode: keyof typeof STORAGE_KEYS = 'pairing';
interface Process {
states: Array<{
committed_in: string;
keys: {};
pcd_commitment: {
counter: string;
};
public_data: {
memberPublicName?: string;
};
roles: {
pairing?: {};
};
state_id: string;
validation_tokens: Array<any>;
}>;
}
class AccountElement extends HTMLElement {
private dom: Node;
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.dom = getCorrectDOM('account-element');
// Ajouter Font Awesome
const fontAwesome = document.createElement('link');
fontAwesome.rel = 'stylesheet';
fontAwesome.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css';
this.shadowRoot!.appendChild(fontAwesome);
const style = document.createElement('style');
style.textContent = accountStyle;
this.shadowRoot!.appendChild(style);
this.shadowRoot!.innerHTML = `
<style>
${accountStyle}
</style>
<!-- Profile Popup -->
<div id="avatar-popup" class="popup">
<div class="popup-content">
<span class="close-popup">&times;</span>
<h2>Profile</h2>
</div>
</div>
<!-- Main Content -->
<div class="container">
<!-- Parameter List -->
<div class="parameter-list">
<ul class="parameter-list-ul profile">
<!-- Profile Preview (visible par défaut) -->
<div class="profile-preview" onclick="window.openAvatarPopup()">
<div class="preview-banner">
<img src="https://via.placeholder.com/800x200" alt="Banner" class="preview-banner-img">
</div>
<div class="preview-info">
<img src="https://via.placeholder.com/150" alt="Avatar" class="preview-avatar">
<div class="preview-text user-info">
<span class="preview-name" id="popup-name">Profile</span>
<span class="preview-lastname" id="popup-lastname"></span>
</div>
</div>
</div>
<!-- Profile Content (masqué par défaut) -->
<div class="profile-content" style="display: none;">
<!-- Banner Preview Section -->
<div class="banner-preview">
<div class="banner-image-container">
<img src="https://via.placeholder.com/800x200" alt="Banner" class="banner-image" id="popup-banner-img">
<div class="banner-content">
<div class="avatar-container">
<img src="https://via.placeholder.com/150" alt="Avatar" class="avatar" id="popup-avatar-img">
</div>
<div class="user-info">
<span class="editable" id="popup-name"></span>
<span class="editable" id="popup-lastname"></span>
</div>
</div>
</div>
<div class="banner-controls">
<label for="banner-upload" class="banner-upload-label button-style">
Change Banner Image
<input type="file" id="banner-upload" accept="image/*" style="display: none;">
</label>
</div>
</div>
<!-- Avatar Upload Section -->
<div class="popup-avatar">
<label for="avatar-upload" class="avatar-upload-label">
<img src="https://via.placeholder.com/150" alt="Avatar" class="avatar" id="popup-avatar-img">
<div class="avatar-overlay">
<span>Change Avatar</span>
</div>
</label>
<input type="file" id="avatar-upload" accept="image/*" style="display: none;">
</div>
<!-- User Info Section -->
<div class="popup-info">
<p><strong>Name:</strong> <span class="editable" id="popup-name"></span></p>
<!--<p><strong>Last Name:</strong> <span class="editable" id="popup-lastname"></span></p>-->
<p><strong>Address:</strong> 🏠 🌍 🗽🎊😩-🎊😑🎄😩</p>
</div>
<!-- Buttons Container -->
<div class="popup-buttons">
<button class="delete-account-btn" onclick="window.confirmDeleteAccount()">Delete Account</button>
</div>
</div>
</ul>
<ul class="parameter-list-ul" onclick="window.showPairing()">Pairing 🔗</ul>
<!-- <ul class="parameter-list-ul" onclick="window.showWallet()">Wallet 👛</ul> -->
<ul class="parameter-list-ul" onclick="window.showProcess()">Process ⚙️</ul>
<ul class="parameter-list-ul" onclick="window.showProcessCreation()">Process Creation</ul>
<ul class="parameter-list-ul" onclick="window.showDocumentValidation()">Document Validation</ul>
<!-- <ul class="parameter-list-ul" onclick="window.showData()">Data 💾</ul> -->
</div>
<!-- Parameter Area -->
<div class="parameter-area">
<div class="content-container">
<div id="pairing-content"></div>
<!-- <div id="wallet-content"></div> -->
<div id="process-content"></div>
<div id="process-creation-content"></div>
<div id="document-validation-content"></div>
<!-- <div id="data-content"></div> -->
</div>
</div>
</div>
`;
window.showPairing = () => this.showPairing();
window.showWallet = () => this.showWallet();
window.showProcess = () => this.showProcess();
window.showProcessCreation = () => this.showProcessCreation();
window.showDocumentValidation = () => this.showDocumentValidation();
window.showData = () => this.showData();
window.addWalletRow = () => this.addWalletRow();
window.confirmWalletRow = () => this.confirmWalletRow();
window.cancelWalletRow = () => this.cancelWalletRow();
window.editDeviceName = (cell: HTMLTableCellElement) => this.editDeviceName(cell);
window.showProcessNotifications = (processName: string) => this.showProcessNotifications(processName);
window.handleLogout = () => this.handleLogout();
window.confirmDeleteAccount = () => this.confirmDeleteAccount();
window.showContractPopup = (contractId: string) => this.showContractPopup(contractId);
window.addRowPairing = () => this.addRowPairing();
window.deleteRowPairing = (button: HTMLButtonElement) => this.deleteRowPairing(button);
window.confirmRowPairing = () => this.confirmRowPairing();
window.cancelRowPairing = () => this.cancelRowPairing();
window.updateNavbarBanner = (bannerUrl: string) => this.updateNavbarBanner(bannerUrl);
window.saveBannerToLocalStorage = (bannerUrl: string) => this.saveBannerToLocalStorage(bannerUrl);
window.loadSavedBanner = () => this.loadSavedBanner();
window.closeNotificationPopup = (event: Event) => this.closeNotificationPopup(event);
window.markAsRead = (processName: string, messageId: number, element: HTMLElement) => this.markAsRead(processName, messageId, element);
window.exportRecovery = () => this.exportRecovery();
window.generateRecoveryWords = () => this.generateRecoveryWords();
window.exportUserData = () => this.exportUserData();
window.updateActionButtons = () => this.updateActionButtons();
window.openAvatarPopup = () => this.openAvatarPopup();
window.closeAvatarPopup = () => this.closeAvatarPopup();
window.showQRCodeModal = (pairingId: string) => this.showQRCodeModal(pairingId);
if (!localStorage.getItem('rows')) {
localStorage.setItem('rows', JSON.stringify(defaultRows));
}
}
connectedCallback() {
this.initializeEventListeners();
this.loadSavedBanner();
this.loadUserInfo();
const savedAvatar = localStorage.getItem('userAvatar');
const savedBanner = localStorage.getItem('userBanner');
const savedName = localStorage.getItem('userName');
const savedLastName = localStorage.getItem('userLastName');
if (savedAvatar) {
const navAvatar = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement;
if (navAvatar) navAvatar.src = savedAvatar;
}
if (savedBanner) {
const navBanner = this.shadowRoot?.querySelector('.banner-image') as HTMLImageElement;
if (navBanner) navBanner.src = savedBanner;
}
if (savedName) {
this.updateNavbarName(savedName);
}
if (savedLastName) {
this.updateNavbarLastName(savedLastName);
}
}
private showAlert(message: string): void {
// Créer la popup si elle n'existe pas
let alertPopup = this.shadowRoot?.querySelector('.alert-popup');
if (!alertPopup) {
alertPopup = document.createElement('div');
alertPopup.className = 'alert-popup';
this.shadowRoot?.appendChild(alertPopup);
}
// Définir le message et afficher la popup
alertPopup.textContent = message;
(alertPopup as HTMLElement).style.display = 'block';
// Cacher la popup après 3 secondes
setTimeout(() => {
(alertPopup as HTMLElement).style.display = 'none';
}, 3000);
}
// Fonctions de gestion des comptes et de l'interface utilisateur
private confirmDeleteAccount(): void {
const modal = document.createElement('div');
modal.className = 'confirm-delete-modal';
modal.innerHTML = `
<h3>Delete Account</h3>
<p>Are you sure you want to delete your account? This action cannot be undone.</p>
<div class="confirm-delete-buttons">
<button class="cancel-btn">Cancel</button>
<button class="confirm-btn">Delete</button>
</div>
`;
this.shadowRoot?.appendChild(modal);
modal.style.display = 'block';
const cancelBtn = modal.querySelector('.cancel-btn');
const confirmBtn = modal.querySelector('.confirm-btn');
cancelBtn?.addEventListener('click', () => {
modal.remove();
});
confirmBtn?.addEventListener('click', () => {
this.deleteAccount();
modal.remove();
});
}
private deleteAccount(): void {
localStorage.clear();
window.location.href = '/login.html';
}
private updateNavbarBanner(imageUrl: string): void {
const navbarSection = this.shadowRoot?.querySelector('.nav-wrapper .avatar-section');
if (!navbarSection) return;
let bannerImg = navbarSection.querySelector<HTMLImageElement>('.banner-image');
if (!bannerImg) {
bannerImg = document.createElement('img');
bannerImg.className = 'banner-image';
navbarSection.insertBefore(bannerImg, navbarSection.firstChild);
}
bannerImg.src = imageUrl;
}
private saveBannerToLocalStorage(dataUrl: string): void {
localStorage.setItem('userBanner', dataUrl);
}
private loadSavedBanner(): void {
const savedBanner = localStorage.getItem('userBanner');
if (savedBanner) {
const bannerImg = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement;
if (bannerImg) {
bannerImg.src = savedBanner;
}
this.updateNavbarBanner(savedBanner);
}
}
private closeNotificationPopup(event: Event): void {
const target = event.target as HTMLElement;
const isOverlay = target.classList.contains('notification-popup-overlay');
const isCloseButton = target.classList.contains('close-popup');
if (!isOverlay && !isCloseButton) return;
const popup = this.shadowRoot?.querySelector('.notification-popup-overlay');
if (popup) popup.remove();
}
private markAsRead(processName: string, messageId: number, element: HTMLElement): void {
const process = mockProcessRows.find(p => p.process === processName);
if (!process) return;
const message = process.notification.messages.find(m => m.id === messageId);
if (!message || message.read) return;
message.read = true;
element.classList.remove('unread');
element.classList.add('read');
const statusIcon = element.querySelector('.notification-status');
if (statusIcon) {
statusIcon.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
</svg>`;
}
const notifCount = this.calculateNotifications(process.notification.messages);
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
if (countElement) {
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
const bellContainer = countElement.closest('.notification-container');
const bell = bellContainer?.querySelector('svg'); // Changé de .fa-bell à svg
if (bell && bellContainer && notifCount.unread === 0) {
bellContainer.classList.remove('has-unread');
(bell as SVGElement).style.fill = '#666'; // Utiliser fill au lieu de color pour SVG
}
}
}
// Fonctions de gestion des données et de l'interface
private calculateNotifications(messages: NotificationMessage[]): { unread: number; total: number } {
const total = messages.length;
const unread = messages.filter(msg => !msg.read).length;
return { unread, total };
}
// Fonctions de récupération
private exportRecovery(): void {
Swal.fire({
title: 'Recovery Words Export',
text: '4 words will be displayed. We strongly recommend writing them down on paper before exporting the account. Do you want to continue?',
icon: 'warning',
showCancelButton: true,
confirmButtonText: 'Confirm',
cancelButtonText: 'Cancel',
confirmButtonColor: '#C89666',
cancelButtonColor: '#6c757d',
// Ajouter des styles personnalisés
customClass: {
container: 'recovery-popup-container',
popup: 'recovery-popup'
}
}).then((result) => {
if (result.isConfirmed) {
const recoveryWords = this.generateRecoveryWords();
localStorage.setItem('recoveryWords', JSON.stringify(recoveryWords));
Swal.fire({
title: 'Your Recovery Words',
html: `
<div class="recovery-words-container">
${recoveryWords.map((word, index) => `
<div class="recovery-word">
<span class="word-number">${index + 1}.</span>
<span class="word">${word}</span>
</div>
`).join('')}
</div>
<div class="recovery-warning">
Please write these words down carefully. They will be needed to recover your account.
</div>
`,
showCancelButton: false,
confirmButtonText: 'I confirm the export',
confirmButtonColor: '#C89666',
allowOutsideClick: false,
allowEscapeKey: false,
customClass: {
container: 'recovery-popup-container',
popup: 'recovery-popup'
}
}).then((result) => {
if (result.isConfirmed) {
// Stocker l'état du bouton dans le localStorage
localStorage.setItem('recoveryExported', 'true');
const exportRecoveryBtn = this.shadowRoot?.querySelector('.recovery-btn') as HTMLButtonElement;
if (exportRecoveryBtn) {
exportRecoveryBtn.disabled = true;
exportRecoveryBtn.style.opacity = '0.5';
exportRecoveryBtn.style.cursor = 'not-allowed';
}
}
});
}
});
}
private generateRecoveryWords(): string[] {
const wordsList = [
'apple', 'banana', 'orange', 'grape', 'kiwi', 'mango', 'peach', 'plum',
'lemon', 'lime', 'cherry', 'melon', 'pear', 'fig', 'date', 'berry'
];
const recoveryWords: string[] = [];
while (recoveryWords.length < 4) {
const randomWord = wordsList[Math.floor(Math.random() * wordsList.length)];
if (!recoveryWords.includes(randomWord)) {
recoveryWords.push(randomWord);
}
}
return recoveryWords;
}
private exportUserData(): void {
const data: { [key: string]: string | null } = {};
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
if (key) {
const value = localStorage.getItem(key);
data[key] = value;
}
}
const jsonData = JSON.stringify(data, null, 2);
const blob = new Blob([jsonData], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'user_data.json';
this.shadowRoot?.appendChild(a);
a.click();
this.shadowRoot?.removeChild(a);
URL.revokeObjectURL(url);
}
private updateActionButtons(): void {
const buttonContainer = this.shadowRoot?.querySelector('.button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<div class="action-buttons-wrapper">
<button onclick="${this.getConfirmFunction()}" class="action-button confirm-button">✓</button>
<button onclick="${this.getCancelFunction()}" class="action-button cancel-button">✗</button>
</div>
`;
}
private getConfirmFunction(): string {
switch (currentMode) {
case 'wallet':
return 'window.confirmWalletRow()';
case 'process':
return 'window.confirmProcessRow()';
default:
return 'window.confirmRowPairing()';
}
}
private getCancelFunction(): string {
switch (currentMode) {
case 'wallet':
return 'window.cancelWalletRow()';
case 'process':
return 'window.cancelProcessRow()';
default:
return 'window.cancelRowPairing()';
}
}
// Fonctions de gestion des tableaux
private async addRowPairing(): Promise<void> {
if (isAddingRow) return;
isAddingRow = true;
// Créer la popup
const modal = document.createElement('div');
modal.className = 'pairing-modal';
modal.innerHTML = `
<div class="pairing-modal-content">
<h3>Add New Device</h3>
<div class="pairing-form">
<div class="form-group">
<label for="sp-address">SP Address</label>
<input type="text" id="sp-address" class="edit-input" placeholder="Enter SP Address">
</div>
<div class="form-group">
<label for="device-name">Device Name</label>
<input type="text" id="device-name" class="edit-input" placeholder="Enter Device Name">
</div>
<div class="form-group">
<label for="sp-emojis">SP Emojis</label>
<input type="text" id="sp-emojis" class="edit-input" readonly>
</div>
<div class="button-group">
<button class="confirm-button">Confirm</button>
<button class="cancel-button">Cancel</button>
</div>
</div>
</div>
`;
this.shadowRoot?.appendChild(modal);
// Ajouter les event listeners
const spAddressInput = modal.querySelector('#sp-address') as HTMLInputElement;
const spEmojisInput = modal.querySelector('#sp-emojis') as HTMLInputElement;
const deviceNameInput = modal.querySelector('#device-name') as HTMLInputElement;
const confirmButton = modal.querySelector('.confirm-button');
const cancelButton = modal.querySelector('.cancel-button');
// Mettre à jour les emojis automatiquement
spAddressInput?.addEventListener('input', async () => {
const emojis = await addressToEmoji(spAddressInput.value);
if (spEmojisInput) spEmojisInput.value = emojis;
});
// Gérer la confirmation
confirmButton?.addEventListener('click', () => {
const spAddress = spAddressInput?.value.trim();
const deviceName = deviceNameInput?.value.trim();
const spEmojis = spEmojisInput?.value.trim();
if (!spAddress || !deviceName) {
this.showAlert('Please fill in all required fields');
return;
}
//if (spAddress.length !== 118) {
// this.showAlert('SP Address must be exactly 118 characters long');
// return;
//}
const newRow: Row = {
column1: spAddress,
column2: deviceName,
column3: spEmojis || ''
};
const storageKey = STORAGE_KEYS[currentMode];
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
rows.push(newRow);
localStorage.setItem(storageKey, JSON.stringify(rows));
this.updateTableContent(rows);
modal.remove();
isAddingRow = false;
});
// Gérer l'annulation
cancelButton?.addEventListener('click', () => {
modal.remove();
isAddingRow = false;
});
}
// Fonctions de mise à jour de l'interface
private updateTableContent(rows: Row[]): void {
const tbody = this.shadowRoot?.querySelector('#pairing-table tbody');
if (!tbody) return;
tbody.innerHTML = rows.map(row => `
<tr>
<td class="device-name" onclick="window.editDeviceName(this)">${row.column2}</td>
<td>${row.column3}</td>
<td>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=50x50&data=${encodeURIComponent(row.column1)}"
alt="QR Code"
title="${row.column1}"
class="qr-code"
onclick="window.showQRCodeModal('${encodeURIComponent(row.column1)}')">
</td>
<td>
<button class="delete-button" onclick="window.deleteRowPairing(this)">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="red">
<path d="M135.2 17.7L128 32H32C14.3 32 0 46.3 0 64S14.3 96 32 96H416c17.7 0 32-14.3 32-32s-14.3-32-32-32H320l-7.2-14.3C307.4 6.8 296.3 0 284.2 0H163.8c-12.1 0-23.2 6.8-28.6 17.7zM416 128H32L53.2 467c1.6 25.3 22.6 45 47.9 45H346.9c25.3 0 46.3-19.7 47.9-45L416 128z"/>
</svg>
</button>
</td>
</tr>
`).join('');
}
private confirmRowPairing(): void {
if (!currentRow) return;
const inputs = currentRow.getElementsByTagName('input');
const values: string[] = Array.from(inputs).map(input => input.value.trim());
// Vérification des champs vides
if (values.some(value => value === '')) {
this.showAlert('Please fill in all fields');
return;
}
// Vérification de la longueur de l'adresse SP
if (values[0].length !== 118) {
this.showAlert('SP Address must be exactly 118 characters long');
return;
}
const newRow: Row = {
column1: values[0],
column2: values[1],
column3: values[2]
};
const storageKey = STORAGE_KEYS[currentMode];
const rows: Row[] = JSON.parse(localStorage.getItem(storageKey) || '[]');
rows.push(newRow);
localStorage.setItem(storageKey, JSON.stringify(rows));
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
this.updateTableContent(rows);
}
private cancelRowPairing(): void {
if (!currentRow) return;
currentRow.remove();
isAddingRow = false;
currentRow = null;
this.resetButtonContainer();
}
private resetButtonContainer(): void {
const buttonContainer = this.shadowRoot?.querySelector('.button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a line</button>
`;
}
private deleteRowPairing(button: HTMLButtonElement): void {
const row = button.closest('tr');
if (!row) return;
const table = row.closest('tbody');
if (!table) return;
const remainingRows = table.getElementsByTagName('tr').length;
if (remainingRows <= 2) {
this.showAlert('You must keep at least 2 devices paired');
return;
}
// Créer la modal de confirmation
const modal = document.createElement('div');
modal.className = 'confirm-delete-modal';
modal.innerHTML = `
<div class="confirm-delete-content">
<h3>Confirm Deletion</h3>
<p>Are you sure you want to delete this device?</p>
<div class="confirm-delete-buttons">
<button class="cancel-btn">Cancel</button>
<button class="confirm-btn">Delete</button>
</div>
</div>
`;
this.shadowRoot?.appendChild(modal);
// Gérer les boutons de la modal
const confirmBtn = modal.querySelector('.confirm-btn');
const cancelBtn = modal.querySelector('.cancel-btn');
confirmBtn?.addEventListener('click', () => {
// Calculer l'index AVANT de supprimer la ligne du DOM
const index = Array.from(table.children).indexOf(row);
const storageKey = STORAGE_KEYS[currentMode];
const rows = JSON.parse(localStorage.getItem(storageKey) || '[]');
// Supprimer du localStorage
if (index > -1) {
rows.splice(index, 1);
localStorage.setItem(storageKey, JSON.stringify(rows));
}
// Animation et suppression du DOM
row.style.transition = 'opacity 0.3s, transform 0.3s';
row.style.opacity = '0';
row.style.transform = 'translateX(-100%)';
setTimeout(() => {
row.remove();
}, 300);
modal.remove();
});
cancelBtn?.addEventListener('click', () => {
modal.remove();
});
}
private editDeviceName(cell: HTMLTableCellElement): void {
if (cell.classList.contains('editing')) return;
const currentValue = cell.textContent || '';
const input = document.createElement('input');
input.type = 'text';
input.value = currentValue;
input.className = 'edit-input';
input.addEventListener('blur', () => this.finishEditing(cell, input));
input.addEventListener('keypress', (e: KeyboardEvent) => {
if (e.key === 'Enter') {
this.finishEditing(cell, input);
}
});
cell.textContent = '';
cell.appendChild(input);
cell.classList.add('editing');
input.focus();
}
private async finishEditing(cell: HTMLTableCellElement, input: HTMLInputElement): Promise<void> {
const newValue = input.value.trim();
if (newValue === '') {
cell.textContent = cell.getAttribute('data-original-value') || '';
cell.classList.remove('editing');
return;
}
try {
const service = await Services.getInstance();
const pairingProcessId = service.getPairingProcessId();
const process = await service.getProcess(pairingProcessId);
// Mettre à jour le nom via le service
if (process) {
await service.updateMemberPublicName(process, newValue);
}
// Mettre à jour l'interface
cell.textContent = newValue;
cell.classList.remove('editing');
} catch (error) {
console.error('Failed to update name:', error);
// Restaurer l'ancienne valeur en cas d'erreur
cell.textContent = cell.getAttribute('data-original-value') || '';
cell.classList.remove('editing');
}
}
// Fonction pour gérer le téléchargement de l'avatar
private handleAvatarUpload(event: Event): void {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const result = e.target?.result as string;
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
const navAvatar = this.shadowRoot?.querySelector('.nav-wrapper .avatar') as HTMLImageElement;
if (popupAvatar) popupAvatar.src = result;
if (navAvatar) navAvatar.src = result;
localStorage.setItem('userAvatar', result);
};
reader.readAsDataURL(file);
}
}
private async showProcessCreation(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('process-creation-content');
if (container) {
getProcessCreation(container);
}
}
private async showDocumentValidation(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('document-validation-content');
if (container) {
getDocumentValidation(container);
}
}
private async showProcess(): Promise<void> {
this.hideAllContent();
const container = this.shadowRoot?.getElementById('process-content');
if (container) {
const service = await Services.getInstance();
const myProcesses = await service.getMyProcesses();
if (myProcesses && myProcesses.length != 0) {
const myProcessesDataUnfiltered: { name: string, publicData: Record<string, any> }[] = await Promise.all(myProcesses.map(async processId => {
const process = await service.getProcess(processId);
const lastState = process ? service.getLastCommitedState(process) : null;
if (!lastState) {
return {
name: '',
publicData: {}
};
}
const description = await service.decryptAttribute(processId, lastState, 'description');
const name = description ? description : 'N/A';
const publicData = process ? await service.getPublicData(process) : null;
if (!publicData) {
return {
name: '',
publicData: {}
};
}
return {
name: name,
publicData: publicData
};
}));
const myProcessesData = myProcessesDataUnfiltered.filter(
(p) => p.name !== '' && Object.keys(p.publicData).length != 0
);
createProcessTab(container, myProcessesData);
} else {
createProcessTab(container, []);
}
}
}
private showProcessNotifications(processName: string): void {
const process = mockProcessRows.find(p => p.process === processName);
if (!process) return;
const modal = document.createElement('div');
modal.className = 'notifications-modal';
let notificationsList = process.notification.messages.map(msg => `
<div class="notification-item ${msg.read ? 'read' : 'unread'}"
onclick="window.markAsRead('${processName}', ${msg.id}, this)">
<div class="notification-status">
${msg.read ?
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="16" height="16" fill="green">
<path d="M438.6 105.4c12.5 12.5 12.5 32.8 0 45.3l-256 256c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L160 338.7 393.4 105.4c12.5-12.5 32.8-12.5 45.3 0z"/>
</svg>` :
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="16" height="16" fill="black">
<path d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512z"/>
</svg>`
}
</div>
<div class="notification-content">
<span>${msg.message}</span>
<small>${msg.date}</small>
</div>
</div>
`).join('');
if (process.notification.messages.length === 0) {
notificationsList = '<p>No notifications</p>';
}
modal.innerHTML = `
<div class="notifications-content">
<h3>${processName} Notifications</h3>
<div class="notifications-list">
${notificationsList}
</div>
<button class="close-notifications">x</button>
</div>
`;
this.shadowRoot?.appendChild(modal);
// Mettre à jour le compteur de notifications
const countElement = this.shadowRoot?.querySelector(`.notification-count[data-process="${processName}"]`);
if (countElement) {
const notifCount = this.calculateNotifications(process.notification.messages);
countElement.textContent = `${notifCount.unread}/${notifCount.total}`;
}
const closeButton = modal.querySelector('.close-notifications');
closeButton?.addEventListener('click', () => {
modal.remove();
this.showProcess(); // Rafraîchir l'affichage pour mettre à jour les compteurs
});
}
private handleLogout(): void {
localStorage.clear();
window.location.href = '../login/login.html';
}
// Fonctions de gestion des contrats
private showContractPopup(contractId: string, event?: Event) {
if (event) {
event.preventDefault();
}
// Check if the contract exists in mockContracts
const contract = mockContracts[contractId as keyof typeof mockContracts];
if (!contract) {
console.error('Contract not found:', contractId);
return;
}
const popup = document.createElement('div');
popup.className = 'contract-popup-overlay';
popup.innerHTML = `
<div class="contract-popup-content">
<button class="close-contract-popup">&times;</button>
<h2>${contract.title}</h2>
<div>
<p><strong>Date:</strong> ${contract.date}</p>
<p><strong>Parties:</strong> ${contract.parties.join(', ')}</p>
<p><strong>Terms:</strong></p>
<ul>
${contract.terms.map(term => `<li>${term}</li>`).join('')}
</ul>
<p><strong>Content:</strong> ${contract.content}</p>
</div>
</div>
`;
this.shadowRoot?.appendChild(popup);
const closeBtn = popup.querySelector('.close-contract-popup');
const closePopup = () => popup.remove();
closeBtn?.addEventListener('click', closePopup);
popup.addEventListener('click', (e) => {
if (e.target === popup) closePopup();
});
}
// Fonction utilitaire pour cacher tous les contenus
private hideAllContent(): void {
const contents = ['pairing-content', 'wallet-content', 'process-content', 'process-creation-content', 'data-content', 'document-validation-content'];
contents.forEach(id => {
const element = this.shadowRoot?.getElementById(id);
if (element) {
element.style.display = 'none';
}
});
}
// Fonctions d'affichage des sections
private async showPairing(): Promise<void> {
const service = await Services.getInstance();
const spAddress = await service.getDeviceAddress();
isAddingRow = false;
currentRow = null;
currentMode = 'pairing';
this.hideAllContent();
const headerElement = this.shadowRoot?.getElementById('parameter-header');
if (headerElement) {
headerElement.textContent = 'Pairing';
}
const pairingContent = this.shadowRoot?.getElementById('pairing-content');
if (pairingContent) {
pairingContent.style.display = 'block';
pairingContent.innerHTML = `
<div class="parameter-header" id="parameter-header">Pairing</div>
<div class="table-container">
<table class="parameter-table" id="pairing-table">
<thead>
<tr>
<th>Device Name</th>
<th>SP Emojis</th>
<th>QR Code</th>
<th>Actions</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="button-container">
<button class="add-row-button button-style" onclick="window.addRowPairing()">Add a device</button>
</div>
</div>
`;
let rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.pairing) || '[]');
const deviceExists = rows.some((row: Row) => row.column1 === spAddress);
if (!deviceExists && spAddress) {
const emojis = await addressToEmoji(spAddress);
try {
// Déboguer le processus de pairing
const pairingProcessId = await service.getPairingProcessId();
console.log('Pairing Process ID:', pairingProcessId);
const pairingProcess = await service.getProcess(pairingProcessId);
console.log('Pairing Process:', pairingProcess);
const userName = pairingProcess?.states?.[0]?.public_data?.memberPublicName
|| localStorage.getItem('userName')
console.log('Username found:', userName);
const newRow = {
column1: spAddress,
column2: userName,
column3: emojis
};
rows = [newRow, ...rows];
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
} catch (error) {
console.error('Error getting pairing process:', error);
const newRow = {
column1: spAddress,
column2: 'This Device',
column3: emojis
};
rows = [newRow, ...rows];
localStorage.setItem(STORAGE_KEYS.pairing, JSON.stringify(rows));
}
}
this.updateTableContent(rows);
}
}
private showWallet(): void {
isAddingRow = false;
currentRow = null;
currentMode = 'wallet';
this.hideAllContent();
// Mettre à jour le titre
const headerTitle = this.shadowRoot?.getElementById('header-title');
if (headerTitle) headerTitle.textContent = 'Wallet';
const walletContent = this.shadowRoot?.getElementById('wallet-content');
if (!walletContent) return;
walletContent.style.display = 'block';
walletContent.innerHTML = `
<div class="parameter-header" id="parameter-header">Wallet</div>
<div class="table-container">
<table class="parameter-table" id="wallet-table">
<thead>
<tr>
<th>Label</th>
<th>Wallet</th>
<th>Type</th>
</tr>
</thead>
<tbody></tbody>
</table>
<div class="button-container">
<button class="add-row-button button-style" onclick="addWalletRow()">Add a line</button>
</div>
</div>
`;
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]');
this.updateWalletTableContent(rows);
}
private updateWalletTableContent(rows: WalletRow[]): void {
const tbody = this.shadowRoot?.querySelector('#wallet-table tbody');
if (!tbody) return;
tbody.innerHTML = rows.map(row => `
<tr>
<td>${row.column1}</td>
<td>${row.column2}</td>
<td class="device-name" onclick="editDeviceName(this)">${row.column3}</td>
</tr>
`).join('');
}
private showData(): void {
//console.log("showData called");
currentMode = 'data';
this.hideAllContent();
const headerTitle = this.shadowRoot?.getElementById('header-title');
if (headerTitle) headerTitle.textContent = 'Data';
const dataContent = this.shadowRoot?.getElementById('data-content');
if (dataContent) {
dataContent.style.display = 'block';
dataContent.innerHTML = `
<div class="parameter-header" id="parameter-header">Data</div>
<div class="table-container">
<table class="parameter-table" id="data-table">
<thead>
<tr>
<th>Name</th>
<th>Visibility</th>
<th>Role</th>
<th>Duration</th>
<th>Legal</th>
<th>Contract</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
`;
const rows = mockDataRows || JSON.parse(localStorage.getItem(STORAGE_KEYS.data) || '[]');
this.updateDataTableContent(rows);
}
}
// Fonctions de gestion du wallet
private addWalletRow(): void {
if (isAddingRow) return;
isAddingRow = true;
const table = this.shadowRoot?.getElementById('wallet-table')?.getElementsByTagName('tbody')[0];
if (!table) return;
currentRow = table.insertRow();
const placeholders = ['Label', 'Wallet', 'Type'];
placeholders.forEach(placeholder => {
const cell = currentRow!.insertCell();
const input = document.createElement('input');
input.type = 'text';
input.placeholder = placeholder;
input.className = 'edit-input';
cell.appendChild(input);
});
// Remplacer le bouton "Add a line" par les boutons de confirmation/annulation
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<div class="action-buttons-wrapper">
<button onclick="confirmWalletRow()" class="action-button confirm-button">✓</button>
<button onclick="cancelWalletRow()" class="action-button cancel-button">✗</button>
</div>
`;
this.updateActionButtons();
}
private confirmWalletRow(): void {
if (!currentRow) return;
const inputs = Array.from(currentRow.getElementsByTagName('input'));
const allFieldsFilled = inputs.every(input => input.value.trim() !== '');
if (allFieldsFilled) {
const newRow: WalletRow = {
column1: inputs[0].value.trim(),
column2: inputs[1].value.trim(),
column3: inputs[2].value.trim()
};
const rows = JSON.parse(localStorage.getItem(STORAGE_KEYS.wallet) || '[]');
rows.push(newRow);
localStorage.setItem(STORAGE_KEYS.wallet, JSON.stringify(rows));
isAddingRow = false;
currentRow = null;
this.showWallet();
} else {
this.showAlert('Please complete all fields before confirming.');
}
}
private cancelWalletRow(): void {
if (!currentRow) return;
currentRow.remove();
isAddingRow = false;
currentRow = null;
// Réinitialiser le conteneur de boutons avec le bouton "Add a line"
const buttonContainer = this.shadowRoot?.querySelector('#wallet-content .button-container');
if (!buttonContainer) return;
buttonContainer.innerHTML = `
<button class="add-row-button button-style" onclick="window.addWalletRow()">Add a line</button>
`;
}
private updateDataTableContent(rows: DataRow[]): void {
const tbody = this.shadowRoot?.querySelector('#data-table tbody');
if (!tbody) return;
tbody.innerHTML = rows.map(row => `
<tr>
<td>${row.column1}</td>
<td>${row.column2}</td>
<td>${row.column3}</td>
<td>${row.column4}</td>
<td>${row.column5}</td>
<td>
<a href="javascript:void(0)" onclick="window.showContractPopup('${row.column6}'); return false;">${row.column6}</a>
</td>
</tr>
`).join('');
}
// Fonctions de gestion de l'avatar et de la bannière
private openAvatarPopup(): void {
const popup = this.shadowRoot?.getElementById('avatar-popup');
if (!popup) return;
// Récuprer les valeurs stockées
const savedName = localStorage.getItem('userName');
const savedLastName = localStorage.getItem('userLastName');
const savedAvatar = localStorage.getItem('userAvatar') || 'https://via.placeholder.com/150';
const savedBanner = localStorage.getItem('userBanner') || 'https://via.placeholder.com/800x200';
const savedAddress = localStorage.getItem('userAddress') || '🏠 🌍 🗽🎊😩-🎊😑😩';
popup.innerHTML = `
<div class="popup-content">
<h1>Profile</h1>
<span class="close-popup">&times;</span>
<div class="banner-container">
<div class="banner-wrapper">
<img src="${savedBanner}" alt="Banner" id="popup-banner-img" class="banner-image clickable">
<input type="file" id="banner-upload" accept="image/*" style="display: none;">
</div>
</div>
<div class="popup-avatar">
<label for="avatar-upload" class="avatar-upload-label">
<img src="${savedAvatar}" alt="Avatar" id="popup-avatar-img">
<input type="file" id="avatar-upload" accept="image/*" style="display: none;">
<div class="avatar-overlay">
<span>Change Avatar</span>
</div>
</label>
</div>
<div class="popup-info">
<div class="info-row">
<strong>Name:</strong>
<input type="text" id="userName" value="${savedName}" class="editable">
</div>
<!--<div class="info-row">
<strong>Last Name:</strong>
<input type="text" id="userLastName" value="${savedLastName}" class="editable">
</div>-->
<div class="info-row">
<strong>Address:</strong>
<span>${savedAddress}</span>
</div>
</div>
<div class="popup-button-container">
<div class="action-buttons-row">
<button class="export-btn" onclick="window.exportUserData()">Export User Data</button>
<button class="export-btn recovery-btn" onclick="window.exportRecovery()">Export Recovery</button>
<button class="delete-account-btn" onclick="window.confirmDeleteAccount()">Delete Account</button>
</div>
<button class="logout-btn" onclick="window.handleLogout()">Log out</button>
</div>
</div>
`;
popup.style.display = 'block';
this.setupEventListeners(popup);
// Ajouter le gestionnaire d'événements pour la bannière
const bannerImg = popup.querySelector('#popup-banner-img');
const bannerInput = popup.querySelector('#banner-upload') as HTMLInputElement;
if (bannerImg && bannerInput) {
bannerImg.addEventListener('click', () => {
bannerInput.click();
});
}
const recoveryExported = localStorage.getItem('recoveryExported') === 'true';
if (recoveryExported) {
const exportRecoveryBtn = popup.querySelector('.recovery-btn') as HTMLButtonElement;
if (exportRecoveryBtn) {
exportRecoveryBtn.disabled = true;
exportRecoveryBtn.style.opacity = '0.5';
exportRecoveryBtn.style.cursor = 'not-allowed';
}
}
}
private setupEventListeners(popup: HTMLElement): void {
// Gestionnaire pour la fermeture
const closeBtn = popup.querySelector('.close-popup');
if (closeBtn) {
closeBtn.addEventListener('click', () => {
popup.style.display = 'none';
});
}
// Gestionnaire pour l'upload d'avatar
const avatarUpload = popup.querySelector('#avatar-upload') as HTMLInputElement;
if (avatarUpload) {
avatarUpload.addEventListener('change', (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const result = e.target?.result as string;
// Mise à jour de l'avatar dans la preview et le popup
const popupAvatar = this.shadowRoot?.getElementById('popup-avatar-img') as HTMLImageElement;
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
if (popupAvatar) popupAvatar.src = result;
if (previewAvatar) previewAvatar.src = result;
localStorage.setItem('userAvatar', result);
};
reader.readAsDataURL(file);
}
});
}
// Gestionnaire pour l'upload de bannière
const bannerUpload = popup.querySelector('#banner-upload') as HTMLInputElement;
if (bannerUpload) {
bannerUpload.addEventListener('change', (e: Event) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (e: ProgressEvent<FileReader>) => {
const result = e.target?.result as string;
// Mise à jour de la bannière dans la preview et le popup
const popupBanner = this.shadowRoot?.getElementById('popup-banner-img') as HTMLImageElement;
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
if (popupBanner) popupBanner.src = result;
if (previewBanner) previewBanner.src = result;
localStorage.setItem('userBanner', result);
};
reader.readAsDataURL(file);
}
});
}
// Gestionnaires pour les champs de texte
const nameInput = popup.querySelector('#userName') as HTMLInputElement;
const lastNameInput = popup.querySelector('#userLastName') as HTMLInputElement;
if (nameInput) {
nameInput.addEventListener('input', () => {
const newName = nameInput.value;
localStorage.setItem('userName', newName);
// Mise à jour du nom dans la preview
const previewName = this.shadowRoot?.querySelector('.preview-name');
if (previewName) previewName.textContent = newName;
});
}
if (lastNameInput) {
lastNameInput.addEventListener('input', () => {
const newLastName = lastNameInput.value;
localStorage.setItem('userLastName', newLastName);
// Mise à jour du nom de famille dans la preview
const previewLastName = this.shadowRoot?.querySelector('.preview-lastname');
if (previewLastName) previewLastName.textContent = newLastName;
});
}
}
private closeAvatarPopup(): void {
const popup = this.shadowRoot?.querySelector('.avatar-popup');
if (popup) popup.remove();
}
private loadAvatar(): void {
const savedAvatar = localStorage.getItem('userAvatar');
if (savedAvatar) {
const avatarImg = this.shadowRoot?.querySelector('.avatar') as HTMLImageElement;
if (avatarImg) {
avatarImg.src = savedAvatar;
}
}
}
private loadUserInfo(): void {
const savedName = localStorage.getItem('userName');
const savedLastName = localStorage.getItem('userLastName');
const savedAvatar = localStorage.getItem('userAvatar');
const savedBanner = localStorage.getItem('userBanner');
// Mise à jour du nom dans la preview
if (savedName) {
const previewName = this.shadowRoot?.querySelector('.preview-name');
if (previewName) {
previewName.textContent = savedName;
}
}
// Mise à jour du nom de famille dans la preview
if (savedLastName) {
const previewLastName = this.shadowRoot?.querySelector('.preview-lastname');
if (previewLastName) {
previewLastName.textContent = savedLastName;
}
}
// Mise à jour de l'avatar dans la preview
if (savedAvatar) {
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
if (previewAvatar) {
previewAvatar.src = savedAvatar;
}
}
// Mise à jour de la bannière dans la preview
if (savedBanner) {
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
if (previewBanner) {
previewBanner.src = savedBanner;
}
}
}
private updateNavbarName(name: string): void {
const nameElement = this.shadowRoot?.querySelector('.nav-wrapper .user-name');
if (nameElement) {
nameElement.textContent = name;
}
}
private updateNavbarLastName(lastName: string): void {
const lastNameElement = this.shadowRoot?.querySelector('.nav-wrapper .user-lastname');
if (lastNameElement) {
lastNameElement.textContent = lastName;
}
}
private updateProfilePreview(data: {
avatar?: string,
banner?: string,
name?: string,
lastName?: string
}): void {
if (data.avatar) {
const previewAvatar = this.shadowRoot?.querySelector('.preview-avatar') as HTMLImageElement;
if (previewAvatar) previewAvatar.src = data.avatar;
}
if (data.banner) {
const previewBanner = this.shadowRoot?.querySelector('.preview-banner-img') as HTMLImageElement;
if (previewBanner) previewBanner.src = data.banner;
}
if (data.name) {
const previewName = this.shadowRoot?.querySelector('.preview-name');
if (previewName) previewName.textContent = data.name;
}
if (data.lastName) {
const previewLastName = this.shadowRoot?.querySelector('.preview-lastname');
if (previewLastName) previewLastName.textContent = data.lastName;
}
}
private initializeEventListeners() {
this.shadowRoot?.addEventListener('DOMContentLoaded', () => {
this.showPairing();
});
const editableFields = this.shadowRoot?.querySelectorAll<HTMLElement>('.editable');
if (editableFields) {
editableFields.forEach(field => {
field.addEventListener('click', () => {
if (!field.classList.contains('editing')) {
const currentValue = field.textContent || '';
const input = document.createElement('input');
input.type = 'text';
input.value = currentValue;
input.className = 'edit-input';
field.textContent = '';
field.appendChild(input);
field.classList.add('editing');
input.focus();
}
});
});
}
const avatarInput = this.shadowRoot?.getElementById('avatar-upload') as HTMLInputElement;
if (avatarInput) {
avatarInput.addEventListener('change', this.handleAvatarUpload.bind(this));
}
}
private showQRCodeModal(pairingId: string): void {
const modal = document.createElement('div');
modal.className = 'qr-modal';
modal.innerHTML = `
<div class="qr-modal-content">
<span class="close-qr-modal">&times;</span>
<img src="https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${pairingId}"
alt="QR Code Large"
class="qr-code-large">
<div class="qr-address">${decodeURIComponent(pairingId)}</div>
</div>
`;
this.shadowRoot?.appendChild(modal);
const closeBtn = modal.querySelector('.close-qr-modal');
closeBtn?.addEventListener('click', () => modal.remove());
modal.addEventListener('click', (e) => {
if (e.target === modal) modal.remove();
});
}
}
customElements.define('account-element', AccountElement);
export { AccountElement };