[FEAT] Simulatore B2B licenze + registrazione ridotta

- simulate-nis2-b2b.php: 6 scenari autonomi (SIM-B1→B6):
  mktg login, invito con recipient data, validazione pubblica,
  registrazione ridotta, provision org, login con org, API Key M2M
- public/simulate-b2b.html: UI terminale dark con flow diagram e SSE streaming
- public/register.html:
  - Registrazione ridotta: con invito che ha recipient data mostra banner
    "Ciao [Nome]!" + campi pre-compilati read-only + solo password richiesta
  - Post-register con inviteToken: chiama provision automaticamente,
    salva nis2_org_id in localStorage, redirect a dashboard.html

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
DevEnv nis2-agile
2026-03-10 15:26:23 +01:00
co-authored by Claude Sonnet 4.6
parent 8304da91b4
commit 8b9a617fd5
3 changed files with 752 additions and 0 deletions
+74
View File
@@ -222,6 +222,20 @@
<!-- STEP 1: Dati account -->
<div id="step-1">
<!-- Banner accesso B2B ridotto (visibile solo con invito + recipient data) -->
<div id="invite-welcome" style="display:none; background:linear-gradient(135deg,#ecfeff,#cffafe); border:1.5px solid #06B6D4; border-radius:12px; padding:.875rem 1rem; margin-bottom:1rem;">
<div style="display:flex; align-items:center; gap:.75rem; margin-bottom:.4rem;">
<div style="width:34px;height:34px;border-radius:8px;background:#06B6D4;color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0;">
<i class="fas fa-ticket-alt" style="font-size:.85rem;"></i>
</div>
<div>
<div id="invite-welcome-name" style="font-weight:700;font-size:.92rem;color:#0e7490;"></div>
<div id="invite-welcome-plan" style="font-size:.73rem;color:#0891b2;margin-top:1px;"></div>
</div>
</div>
<p style="margin:0;font-size:.8rem;color:#0e7490;line-height:1.45;">I tuoi dati sono stati pre-compilati dall'invito. <strong>Scegli solo la password</strong> per accedere alla piattaforma.</p>
</div>
<form id="register-form" novalidate>
<div style="display:flex; gap:.75rem;">
<div class="form-group" style="flex:1;">
@@ -331,6 +345,8 @@
let selectedRole = null;
let inviteValid = false;
let inviteToken = null;
let inviteRecipient = null; // dati destinatario dell'invito (pre-fill)
let isReducedReg = false; // true = registrazione ridotta (solo password)
let pivaData = null;
let pivaLookupTimer = null;
let successRedirect = 'onboarding.html';
@@ -425,7 +441,10 @@
if (r.last_name) document.getElementById('lastname').value = r.last_name;
if (r.email) document.getElementById('email').value = r.email;
if (r.vat) { document.getElementById('piva').value = r.vat; lookupPiva(r.vat); }
inviteRecipient = r;
isReducedReg = !!(r.first_name && r.last_name && r.email);
}
if (isReducedReg) applyReducedRegMode();
} else {
inviteValid = false;
inviteToken = null;
@@ -493,6 +512,29 @@
}, 800);
}
// --- Registrazione Ridotta (invito con dati pre-compilati) ---
function applyReducedRegMode() {
const name = ((inviteRecipient.first_name || '') + ' ' + (inviteRecipient.last_name || '')).trim();
document.getElementById('invite-welcome-name').textContent = 'Ciao ' + name + '!';
document.getElementById('invite-welcome-plan').textContent = 'Accesso B2B — account pre-configurato dall\'invito';
document.getElementById('invite-welcome').style.display = 'block';
// Campi pre-compilati → read-only
['firstname', 'lastname', 'email'].forEach(id => {
const el = document.getElementById(id);
el.readOnly = true;
el.style.background = '#f9fafb';
el.style.color = '#6b7280';
});
// Nascondi P.IVA se già presente nell'invito
if (inviteRecipient.vat) {
document.getElementById('piva-group').style.display = 'none';
}
// Seleziona ruolo default se non già selezionato
if (!selectedRole) selectRole('org_admin');
// Avanza direttamente allo step dati senza richiedere click
goToStep1();
}
// --- Step Navigation ---
function setStep(n) {
[0, 1, 2].forEach(i => {
@@ -633,6 +675,38 @@
// Determine redirect
if (inviteToken) {
successRedirect = 'dashboard.html';
// Salva JWT tokens
if (result.data?.access_token) {
api.setTokens(result.data.access_token, result.data.refresh_token);
localStorage.setItem('nis2_user_role', result.data.user?.role || selectedRole || 'org_admin');
}
// Chiama provision per creare l'organizzazione
try {
const companyName = pivaData?.company_name || inviteRecipient?.company || 'Azienda';
const vatNum = (piva || inviteRecipient?.vat || '').replace(/[^0-9]/g, '');
if (vatNum.length === 11) {
btn.textContent = 'Configurazione in corso...';
const provRes = await fetch(api.baseUrl + '/services/provision', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
invite_token: inviteToken,
company: {
ragione_sociale: companyName,
partita_iva: vatNum,
sector: pivaData?.sector || 'ict',
nis2_entity_type: 'important'
},
admin: { email, full_name: fullname },
caller: { system: 'self-register' }
})
});
const provData = await provRes.json();
if (provData.success && provData.data?.org_id) {
localStorage.setItem('nis2_org_id', String(provData.data.org_id));
}
}
} catch { /* provision fallita — utente completerà onboarding */ }
} else if (selectedRole === 'consultant') {
successRedirect = 'companies.html';
} else {