1590 lines
59 KiB
TypeScript
Executable File
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">×</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">×</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">×</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">×</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 };
|