Add beautiful loader step for the connexion with the iframe

This commit is contained in:
NicolasCantu 2025-11-12 12:30:19 +01:00
parent 3a61ffe7a6
commit c09fa6f2f8
2 changed files with 193 additions and 38 deletions

View File

@ -1,35 +1,112 @@
<div class="title-container"> <div id="iframe-loader" class="loader-container">
<h1>Create Account / New Session</h1> <div class="loader-content">
</div> <svg class="loader-spinner" viewBox="0 0 50 50">
<circle cx="25" cy="25" r="20" fill="none" stroke-width="5"></circle>
</svg>
<div class="tab-container"> <div id="loader-steps-container">
<div class="tabs"> <p class="loader-step active">Initialisation...</p>
<div class="tab active" data-tab="tab1">Create an account</div>
<div class="tab" data-tab="tab2">Add a device for an existing memeber</div>
</div>
</div>
<div class="page-container">
<div id="tab1" class="card tab-content active">
<div class="card-description">Create an account :</div>
<div class="pairing-request"></div>
<button id="createButton" class="create-btn"></button>
</div>
<div class="separator"></div>
<div id="tab2" class="card tab-content">
<div class="card-description">Add a device for an existing member :</div>
<div class="card-image camera-card">
<img id="scanner" src="assets/camera.jpg" alt="QR Code" width="150" height="150" />
<button id="scan-btn" onclick="scanDevice()">Scan</button> <div class="qr-code-scanner">
<div id="qr-reader" style="width: 200px; display: contents"></div>
<div id="qr-reader-results"></div>
</div>
</div> </div>
<p>Or</p>
<div class="card-description">Chose a member :</div> </div>
<select name="memberSelect" id="memberSelect" size="5" class="custom-select"> </div>
<div id="main-content" style="display: none;">
<div class="title-container">
<h1>Create Account / New Session</h1>
</div>
<div class="tab-container">
<div class="tabs">
<div class="tab active" data-tab="tab1">Create an account</div>
<div class="tab" data-tab="tab2">Add a device for an existing memeber</div>
</div>
</div>
<div class="page-container">
<div id="tab1" class="card tab-content active">
<div class="card-description">Create an account :</div>
<div class="pairing-request"></div>
<button id="createButton" class="create-btn"></button>
</div>
<div class="separator"></div>
<div id="tab2" class="card tab-content">
<div class="card-description">Add a device for an existing member :</div>
<div class="card-image camera-card">
<img id="scanner" src="assets/camera.jpg" alt="QR Code" width="150" height="150" />
<button id="scan-btn" onclick="scanDevice()">Scan</button> <div class="qr-code-scanner">
<div id="qr-reader" style="width: 200px; display: contents"></div>
<div id="qr-reader-results"></div>
</div>
</div>
<p>Or</p>
<div class="card-description">Chose a member :</div>
<select name="memberSelect" id="memberSelect" size="5" class="custom-select">
</select> </select>
<button id="okButton" style="display: none">OK</button> <button id="okButton" style="display: none">OK</button>
</div>
</div> </div>
</div> </div>
<style>
/* --- Style du Loader --- */
.loader-container {
display: flex;
align-items: center;
justify-content: center;
/* Ajustez '400px' à la hauteur de votre modal */
height: 400px;
width: 100%;
/* Assurez-vous que le fond correspond à votre modal */
background-color: #ffffff;
}
.loader-content {
text-align: center;
font-family: Arial, sans-serif;
color: #333;
display: flex;
flex-direction: column;
align-items: center;
}
/* --- Style des Étapes --- */
#loader-steps-container {
margin-top: 20px;
text-align: left; /* Plus joli pour une liste */
width: 250px; /* Largeur fixe pour l'alignement */
height: 100px; /* Hauteur pour éviter les sauts de page */
overflow: hidden;
}
.loader-step {
font-size: 14px;
color: #888; /* Anciennes étapes en gris */
margin: 4px 0;
transition: all 0.3s ease;
white-space: nowrap;
}
.loader-step.active {
font-size: 16px;
color: #333; /* Étape actuelle en noir */
font-weight: 600;
}
/* --- Animation du Spinner --- */
.loader-spinner {
animation: rotate 1.5s linear infinite;
width: 50px;
height: 50px;
}
.loader-spinner circle {
stroke: #007bff; /* Couleur du spinner */
stroke-linecap: round;
animation: dash 1.5s ease-in-out infinite;
}
@keyframes rotate { 100% { transform: rotate(360deg); } }
@keyframes dash {
0% { stroke-dasharray: 1, 150; stroke-dashoffset: 0; }
50% { stroke-dasharray: 90, 150; stroke-dashoffset: -35; }
100% { stroke-dasharray: 90, 150; stroke-dashoffset: -124; }
}
</style>

View File

@ -3,11 +3,37 @@
import Routing from '../../services/modal.service'; import Routing from '../../services/modal.service';
import Services from '../../services/service'; import Services from '../../services/service';
import { addSubscription } from '../../utils/subscription.utils'; import { addSubscription } from '../../utils/subscription.utils';
import { displayEmojis, generateQRCode, generateCreateBtn, addressToEmoji } from '../../utils/sp-address.utils'; import { displayEmojis, generateQRCode, generateCreateBtn, prepareAndSendPairingTx addressToEmoji } from '../../utils/sp-address.utils';
import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component'; import QrScannerComponent from '../../components/qrcode-scanner/qrcode-scanner-component';
export { QrScannerComponent }; export { QrScannerComponent };
function addLoaderStep(container: ShadowRoot, text: string) {
// 1. Rendre l'ancienne étape "inactive"
const currentStep = container.querySelector('.loader-step.active') as HTMLParagraphElement;
if (currentStep) {
currentStep.classList.remove('active');
}
// 2. Trouver le conteneur
const stepsContainer = container.querySelector('#loader-steps-container') as HTMLDivElement;
if (stepsContainer) {
// 3. Créer et ajouter la nouvelle étape "active"
const newStep = document.createElement('p');
newStep.className = 'loader-step active';
newStep.textContent = text;
stepsContainer.appendChild(newStep);
}
}
function updateLoaderText(container: ShadowRoot, text: string) {
const loaderText = container.querySelector('#loader-text') as HTMLParagraphElement;
if (loaderText) {
loaderText.textContent = text;
}
}
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
/** /**
* Fonction d'initialisation principale. * Fonction d'initialisation principale.
* Elle est appelée par home-component.ts et reçoit le ShadowRoot. * Elle est appelée par home-component.ts et reçoit le ShadowRoot.
@ -19,6 +45,9 @@ export async function initHomePage(container: ShadowRoot): Promise<void> {
return; return;
} }
const loaderDiv = container.querySelector('#iframe-loader') as HTMLDivElement;
const mainContentDiv = container.querySelector('#main-content') as HTMLDivElement;
const tabs = container.querySelectorAll('.tab'); const tabs = container.querySelectorAll('.tab');
tabs.forEach((tab) => { tabs.forEach((tab) => {
@ -31,11 +60,60 @@ export async function initHomePage(container: ShadowRoot): Promise<void> {
}); });
const service = await Services.getInstance(); const service = await Services.getInstance();
const spAddress = await service.getDeviceAddress();
generateCreateBtn();
displayEmojis(spAddress);
await populateMemberSelect(container); // --- Début du flux de chargement ---
try {
// 'Initialisation...' est déjà dans le HTML
await delay(500); // Délai pour que "Initialisation..." soit visible
addLoaderStep(container, "Initialisation des services...");
const service = await Services.getInstance();
await delay(700);
addLoaderStep(container, "Vérification de l'appareil...");
const currentDevice = await service.getDeviceFromDatabase();
const pairingId = currentDevice?.pairing_process_commitment || null;
if (pairingId) {
await delay(300);
addLoaderStep(container, "Appairage existant trouvé.");
service.setProcessId(pairingId);
} else {
await delay(300);
addLoaderStep(container, "Création d'un appairage sécurisé...");
await prepareAndSendPairingTx();
addLoaderStep(container, "Appairage créé avec succès.");
}
await delay(500);
addLoaderStep(container, "Finalisation de la connexion...");
container.querySelectorAll('.tab').forEach((t) => t.classList.remove('active'));
container.querySelector('[data-tab="tab2"]')?.classList.add('active');
container.querySelectorAll('.tab-content').forEach((content) => content.classList.remove('active'));
container.querySelector('#tab2')?.classList.add('active');
const spAddress = await service.getDeviceAddress();
generateCreateBtn();
displayEmojis(spAddress);
await populateMemberSelect(container);
await delay(1000);
} catch (e: any) {
console.error('[home.ts] Échec de la logique auto-pairing:', e);
addLoaderStep(container, `Erreur: ${e.message}`);
// En cas d'erreur, on ne cache pas le loader
return;
}
// --- Cacher le loader et afficher le contenu ---
// (Votre AuthModal le fermera si vite que ce ne sera pas visible,
// mais c'est une bonne pratique au cas où)
if (loaderDiv) loaderDiv.style.display = 'none';
if (mainContentDiv) mainContentDiv.style.display = 'block';
console.log('[home.ts] Init terminée. L\'iframe est prête pour le requestLink().');
} }
/** /**