[FEAT] Landing NIS2: accesso su invito + form lead request
- index.html: CTA "Registrati" → "Richiedi accesso" (anchor form) Badge hero "Accesso su invito — Richiedi il tuo codice per iniziare" Sezione #richiedi-accesso con form lead (nome, email, azienda, ruolo, dimensioni, messaggio) + JS submit asincrono + stato successo/errore CTA finale aggiornato con messaggio codice invito - ContactController.php: POST /api/contact/request-invite Validazione campi, rate limit 3/10min per IP, email a info@agile.software tramite EmailService con template HTML branded - index.php: route contact → ContactController + action requestInvite Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
6cf1cd7384
commit
0a194f6f12
147
application/controllers/ContactController.php
Normal file
147
application/controllers/ContactController.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* ContactController — Gestione lead e richieste codice invito
|
||||
*
|
||||
* Endpoint pubblici (no auth):
|
||||
* POST /api/contact/request-invite → richiesta accesso con codice invito
|
||||
*/
|
||||
|
||||
require_once APP_PATH . '/controllers/BaseController.php';
|
||||
require_once APP_PATH . '/services/EmailService.php';
|
||||
|
||||
class ContactController extends BaseController
|
||||
{
|
||||
private EmailService $email;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->email = new EmailService();
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/contact/request-invite
|
||||
* Raccoglie i dati del lead e invia notifica a info@agile.software
|
||||
*/
|
||||
public function requestInvite(): void
|
||||
{
|
||||
// Rate limit: max 3 richieste / 10 min per IP
|
||||
$ip = $this->getClientIP();
|
||||
$key = 'contact_' . md5($ip);
|
||||
$cacheFile = sys_get_temp_dir() . '/nis2_contact_' . $key;
|
||||
$now = time();
|
||||
|
||||
if (file_exists($cacheFile)) {
|
||||
$data = json_decode(file_get_contents($cacheFile), true);
|
||||
$data['requests'] = array_filter($data['requests'] ?? [], fn($t) => $t > ($now - 600));
|
||||
if (count($data['requests']) >= 3) {
|
||||
$this->jsonError('Troppe richieste. Riprova tra qualche minuto.', 429);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$data = ['requests' => []];
|
||||
}
|
||||
$data['requests'][] = $now;
|
||||
file_put_contents($cacheFile, json_encode($data));
|
||||
|
||||
// Validazione input
|
||||
$body = $this->getRequestBody();
|
||||
$nome = trim($body['nome'] ?? '');
|
||||
$email = trim($body['email'] ?? '');
|
||||
$azienda = trim($body['azienda'] ?? '');
|
||||
$ruolo = trim($body['ruolo'] ?? '');
|
||||
$dimensioni = trim($body['dimensioni'] ?? '');
|
||||
$messaggio = trim($body['messaggio'] ?? '');
|
||||
|
||||
if (!$nome || !$email || !$azienda || !$ruolo) {
|
||||
$this->jsonError('Compila tutti i campi obbligatori (nome, email, azienda, ruolo).', 422);
|
||||
return;
|
||||
}
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$this->jsonError('Indirizzo email non valido.', 422);
|
||||
return;
|
||||
}
|
||||
// Sanitize
|
||||
$nome = htmlspecialchars($nome, ENT_QUOTES, 'UTF-8');
|
||||
$email = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
|
||||
$azienda = htmlspecialchars($azienda, ENT_QUOTES, 'UTF-8');
|
||||
$ruolo = htmlspecialchars($ruolo, ENT_QUOTES, 'UTF-8');
|
||||
$dimensioni = htmlspecialchars($dimensioni, ENT_QUOTES, 'UTF-8');
|
||||
$messaggio = htmlspecialchars($messaggio, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$date = date('d/m/Y H:i');
|
||||
|
||||
$html = "
|
||||
<div style='font-family:Inter,Arial,sans-serif;max-width:600px;margin:0 auto;'>
|
||||
<div style='background:#0F172A;padding:24px 32px;border-radius:12px 12px 0 0;'>
|
||||
<h2 style='color:#06B6D4;margin:0;font-size:20px;'>
|
||||
🛡️ Nuova richiesta codice invito — NIS2 Agile
|
||||
</h2>
|
||||
<p style='color:#94A3B8;margin:6px 0 0;font-size:13px;'>Ricevuta il {$date}</p>
|
||||
</div>
|
||||
<div style='background:#1E293B;padding:28px 32px;border-radius:0 0 12px 12px;'>
|
||||
<table style='width:100%;border-collapse:collapse;'>
|
||||
<tr>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;width:140px;vertical-align:top;'>Nome</td>
|
||||
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;font-weight:600;'>{$nome}</td>
|
||||
</tr>
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>Email</td>
|
||||
<td style='padding:10px 0;color:#06B6D4;font-size:14px;'><a href='mailto:{$email}' style='color:#06B6D4;'>{$email}</a></td>
|
||||
</tr>
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>Azienda/Studio</td>
|
||||
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;font-weight:600;'>{$azienda}</td>
|
||||
</tr>
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>Ruolo</td>
|
||||
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;'>{$ruolo}</td>
|
||||
</tr>
|
||||
" . ($dimensioni ? "
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>Dimensioni</td>
|
||||
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;'>{$dimensioni}</td>
|
||||
</tr>" : "") . "
|
||||
" . ($messaggio ? "
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>Messaggio</td>
|
||||
<td style='padding:10px 0;color:#F8FAFC;font-size:14px;line-height:1.6;'>{$messaggio}</td>
|
||||
</tr>" : "") . "
|
||||
<tr style='border-top:1px solid rgba(6,182,212,0.1);'>
|
||||
<td style='padding:10px 0;color:#94A3B8;font-size:13px;vertical-align:top;'>IP</td>
|
||||
<td style='padding:10px 0;color:#64748B;font-size:12px;'>{$ip}</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div style='margin-top:24px;padding:16px;background:rgba(6,182,212,0.06);border-radius:8px;border:1px solid rgba(6,182,212,0.15);'>
|
||||
<p style='margin:0;color:#94A3B8;font-size:13px;'>
|
||||
Rispondi a questo lead generando un codice invito da:
|
||||
<a href='https://nis2.agile.software/licenseExt.html' style='color:#06B6D4;'>licenseExt.html</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>";
|
||||
|
||||
$sent = $this->email->send(
|
||||
'info@agile.software',
|
||||
"🛡️ Richiesta accesso NIS2 Agile — {$nome} ({$azienda})",
|
||||
$html,
|
||||
'noreply@agile.software'
|
||||
);
|
||||
|
||||
if (!$sent) {
|
||||
$this->jsonError('Errore nell\'invio della richiesta. Prova a contattarci direttamente a info@agile.software.', 500);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->jsonSuccess(null, 'Richiesta inviata! Ti contatteremo entro 24 ore con il tuo codice di accesso.');
|
||||
}
|
||||
|
||||
private function getClientIP(): string
|
||||
{
|
||||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||||
$ips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
|
||||
return trim($ips[0]);
|
||||
}
|
||||
return $_SERVER['REMOTE_ADDR'] ?? '0.0.0.0';
|
||||
}
|
||||
}
|
||||
@ -74,6 +74,15 @@
|
||||
}
|
||||
.nav-name span { color: var(--cyan); }
|
||||
.nav-actions { display: flex; gap: 12px; align-items: center; }
|
||||
.btn-invite {
|
||||
background: var(--brand-gradient);
|
||||
color: white;
|
||||
box-shadow: 0 4px 16px rgba(6,182,212,0.25);
|
||||
}
|
||||
.btn-invite:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 8px 24px rgba(6,182,212,0.35);
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@ -531,6 +540,89 @@
|
||||
}
|
||||
.norma-strip i { color: var(--cyan); }
|
||||
|
||||
/* ── BADGE INVITO ── */
|
||||
.invite-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: rgba(245,158,11,0.1);
|
||||
border: 1px solid rgba(245,158,11,0.25);
|
||||
border-radius: 8px;
|
||||
padding: 8px 16px;
|
||||
font-size: 13px;
|
||||
color: var(--orange);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.invite-badge i { font-size: 12px; }
|
||||
|
||||
/* ── FORM LEAD ── */
|
||||
#richiedi-accesso { background: var(--surface-dark); }
|
||||
.form-box {
|
||||
background: var(--brand-primary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 20px;
|
||||
padding: 48px;
|
||||
max-width: 680px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.form-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.form-group { display: flex; flex-direction: column; gap: 8px; }
|
||||
.form-group.full { grid-column: 1 / -1; }
|
||||
.form-group label {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: var(--text-light);
|
||||
}
|
||||
.form-group label span { color: var(--red); margin-left: 2px; }
|
||||
.form-group input,
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
background: var(--surface-dark);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 14px;
|
||||
color: var(--text-white);
|
||||
font-family: inherit;
|
||||
transition: border-color 0.2s;
|
||||
width: 100%;
|
||||
}
|
||||
.form-group input:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
outline: none;
|
||||
border-color: var(--cyan);
|
||||
}
|
||||
.form-group input::placeholder,
|
||||
.form-group textarea::placeholder { color: var(--text-muted); }
|
||||
.form-group select option { background: #1E293B; }
|
||||
.form-group textarea { resize: vertical; min-height: 90px; }
|
||||
.form-submit { width: 100%; padding: 14px; font-size: 16px; margin-top: 8px; }
|
||||
.form-note { font-size: 12px; color: var(--text-muted); text-align: center; margin-top: 12px; }
|
||||
.form-success {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding: 32px;
|
||||
}
|
||||
.form-success i { font-size: 48px; color: var(--green); margin-bottom: 16px; display: block; }
|
||||
.form-success h3 { font-size: 22px; font-weight: 700; color: var(--text-white); margin-bottom: 8px; }
|
||||
.form-success p { color: var(--text-muted); font-size: 15px; }
|
||||
.form-error-msg {
|
||||
display: none;
|
||||
background: rgba(239,68,68,0.08);
|
||||
border: 1px solid rgba(239,68,68,0.2);
|
||||
border-radius: 8px;
|
||||
padding: 12px 16px;
|
||||
font-size: 13px;
|
||||
color: var(--red);
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* ── RESPONSIVE ── */
|
||||
@media (max-width: 900px) {
|
||||
nav { padding: 0 20px; }
|
||||
@ -546,6 +638,8 @@
|
||||
.lg231-card { flex-direction: column; padding: 28px; }
|
||||
.footer-inner { flex-direction: column; align-items: flex-start; }
|
||||
.nav-actions .btn-ghost { display: none; }
|
||||
.form-grid { grid-template-columns: 1fr; }
|
||||
.form-box { padding: 28px 20px; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
@ -561,8 +655,8 @@
|
||||
<a href="/login.html" class="btn btn-ghost btn-sm">
|
||||
<i class="fa-solid fa-right-to-bracket"></i> Accedi
|
||||
</a>
|
||||
<a href="/register.html" class="btn btn-primary btn-sm">
|
||||
<i class="fa-solid fa-rocket"></i> Registrati
|
||||
<a href="#richiedi-accesso" class="btn btn-primary btn-sm">
|
||||
<i class="fa-solid fa-envelope"></i> Richiedi accesso
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
@ -583,12 +677,16 @@
|
||||
<p class="hero-subtitle">
|
||||
Piattaforma SaaS multi-tenant per guidare la tua azienda alla conformità con la Direttiva NIS2 (EU 2022/2555). Gap analysis AI, risk management, incident response Art.23, policy e formazione — tutto integrato.
|
||||
</p>
|
||||
<div class="invite-badge">
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
Accesso su invito — Richiedi il tuo codice per iniziare
|
||||
</div>
|
||||
<div class="hero-ctas">
|
||||
<a href="/register.html" class="btn btn-primary btn-lg">
|
||||
<i class="fa-solid fa-rocket"></i> Inizia gratuitamente
|
||||
<a href="#richiedi-accesso" class="btn btn-primary btn-lg">
|
||||
<i class="fa-solid fa-envelope"></i> Richiedi accesso
|
||||
</a>
|
||||
<a href="/login.html" class="btn btn-ghost btn-lg">
|
||||
<i class="fa-solid fa-right-to-bracket"></i> Accedi
|
||||
<i class="fa-solid fa-right-to-bracket"></i> Ho un codice — Accedi
|
||||
</a>
|
||||
</div>
|
||||
<div class="hero-stats">
|
||||
@ -849,21 +947,95 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- FORM RICHIESTA ACCESSO -->
|
||||
<section id="richiedi-accesso">
|
||||
<div class="container">
|
||||
<div class="section-header">
|
||||
<div class="section-eyebrow"><i class="fa-solid fa-envelope"></i> Richiedi accesso</div>
|
||||
<h2 class="section-title">Ottieni il tuo <span class="gradient">codice invito</span></h2>
|
||||
<p class="section-desc">La piattaforma è ad accesso controllato. Compila il form e ti invieremo il codice invito entro 24 ore.</p>
|
||||
</div>
|
||||
<div class="form-box">
|
||||
<div class="form-error-msg" id="formError"></div>
|
||||
<form id="inviteForm">
|
||||
<div class="form-grid">
|
||||
<div class="form-group">
|
||||
<label>Nome e Cognome <span>*</span></label>
|
||||
<input type="text" name="nome" placeholder="Mario Rossi" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Email aziendale <span>*</span></label>
|
||||
<input type="email" name="email" placeholder="mario@azienda.it" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Azienda / Studio <span>*</span></label>
|
||||
<input type="text" name="azienda" placeholder="Nome azienda o studio" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Ruolo <span>*</span></label>
|
||||
<select name="ruolo" required>
|
||||
<option value="">Seleziona il tuo ruolo...</option>
|
||||
<option value="Responsabile Compliance / CISO">Responsabile Compliance / CISO</option>
|
||||
<option value="IT Manager / Responsabile Sicurezza">IT Manager / Responsabile Sicurezza</option>
|
||||
<option value="Consulente Cybersecurity / MSSP">Consulente Cybersecurity / MSSP</option>
|
||||
<option value="Direzione / CEO / CTO">Direzione / CEO / CTO</option>
|
||||
<option value="DPO / Legale">DPO / Legale</option>
|
||||
<option value="Altra figura">Altra figura</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Dimensioni azienda</label>
|
||||
<select name="dimensioni">
|
||||
<option value="">Seleziona...</option>
|
||||
<option value="Micro (<10 dipendenti)">Micro (<10 dipendenti)</option>
|
||||
<option value="Piccola (10-49 dipendenti)">Piccola (10–49 dipendenti)</option>
|
||||
<option value="Media (50-249 dipendenti)">Media (50–249 dipendenti)</option>
|
||||
<option value="Grande (250+ dipendenti)">Grande (250+ dipendenti)</option>
|
||||
<option value="Studio / Consulente multi-cliente">Studio / Consulente multi-cliente</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group full">
|
||||
<label>Messaggio (opzionale)</label>
|
||||
<textarea name="messaggio" placeholder="Descrivi brevemente la tua esigenza o il settore di appartenenza NIS2..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary form-submit" id="submitBtn">
|
||||
<i class="fa-solid fa-paper-plane"></i> Invia richiesta
|
||||
</button>
|
||||
<p class="form-note">
|
||||
<i class="fa-solid fa-lock" style="color:var(--cyan);margin-right:4px;font-size:11px;"></i>
|
||||
I tuoi dati sono trattati nel rispetto del GDPR. Nessuna cessione a terzi.
|
||||
</p>
|
||||
</form>
|
||||
<div class="form-success" id="formSuccess">
|
||||
<i class="fa-solid fa-circle-check"></i>
|
||||
<h3>Richiesta inviata!</h3>
|
||||
<p>Ti contatteremo entro 24 ore all'indirizzo fornito con il tuo codice di accesso personalizzato.<br><br>
|
||||
<strong style="color:var(--text-white);">Hai già ricevuto un codice?</strong><br>
|
||||
<a href="/register.html" style="color:var(--cyan);text-decoration:none;">Registrati ora con il tuo codice invito →</a></p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="text-align:center;font-size:13px;color:var(--text-muted);margin-top:24px;">
|
||||
Hai già un account? <a href="/login.html" style="color:var(--cyan);text-decoration:none;">Accedi alla dashboard →</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- CTA FINALE -->
|
||||
<section id="cta-finale">
|
||||
<div class="container">
|
||||
<div class="cta-box">
|
||||
<h2>Inizia oggi la tua<br><span style="background: var(--brand-gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text;">compliance NIS2</span></h2>
|
||||
<p>Registrazione gratuita. Nessuna carta di credito richiesta. Operativo in 5 minuti.</p>
|
||||
<h2>La tua compliance NIS2<br><span style="background: var(--brand-gradient); -webkit-background-clip:text; -webkit-text-fill-color:transparent; background-clip:text;">inizia con un codice</span></h2>
|
||||
<p>Richiedi l'accesso oggi. Nessuna carta di credito richiesta. Operativo in 5 minuti dal ricevimento del codice.</p>
|
||||
<div class="cta-actions">
|
||||
<a href="/register.html" class="btn btn-primary btn-lg">
|
||||
<i class="fa-solid fa-rocket"></i> Registrati gratis
|
||||
<a href="#richiedi-accesso" class="btn btn-primary btn-lg">
|
||||
<i class="fa-solid fa-envelope"></i> Richiedi il codice invito
|
||||
</a>
|
||||
<a href="/login.html" class="btn btn-ghost btn-lg">
|
||||
<i class="fa-solid fa-right-to-bracket"></i> Accedi
|
||||
</a>
|
||||
</div>
|
||||
<p class="cta-note">Hai già una licenza ricevuta da un consulente? <a href="/register.html" style="color:var(--cyan);text-decoration:none;">Registrati con il tuo codice invito</a></p>
|
||||
<p class="cta-note">Sei un consulente o MSSP? Il form di richiesta ti permette di attivare accesso per il tuo intero portfolio clienti.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
@ -877,8 +1049,8 @@
|
||||
<span style="color:var(--text-muted);font-size:12px;margin-left:8px;">by Agile Technology SRL</span>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a href="#richiedi-accesso">Richiedi accesso</a>
|
||||
<a href="/login.html">Accedi</a>
|
||||
<a href="/register.html">Registrati</a>
|
||||
<a href="https://lg231.agile.software/" target="_blank">231 Agile</a>
|
||||
<a href="mailto:info@agile.software">info@agile.software</a>
|
||||
</div>
|
||||
@ -888,5 +1060,51 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script>
|
||||
document.getElementById('inviteForm').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('submitBtn');
|
||||
const errEl = document.getElementById('formError');
|
||||
const successEl = document.getElementById('formSuccess');
|
||||
const form = e.target;
|
||||
|
||||
errEl.style.display = 'none';
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Invio in corso...';
|
||||
|
||||
const data = {
|
||||
nome: form.nome.value.trim(),
|
||||
email: form.email.value.trim(),
|
||||
azienda: form.azienda.value.trim(),
|
||||
ruolo: form.ruolo.value,
|
||||
dimensioni: form.dimensioni.value,
|
||||
messaggio: form.messaggio.value.trim()
|
||||
};
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/contact/request-invite', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
const json = await res.json();
|
||||
|
||||
if (res.ok && json.success) {
|
||||
form.style.display = 'none';
|
||||
successEl.style.display = 'block';
|
||||
} else {
|
||||
errEl.textContent = json.message || 'Errore nell\'invio. Riprova o scrivici a info@agile.software.';
|
||||
errEl.style.display = 'block';
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fa-solid fa-paper-plane"></i> Invia richiesta';
|
||||
}
|
||||
} catch {
|
||||
errEl.textContent = 'Errore di rete. Riprova o scrivici a info@agile.software.';
|
||||
errEl.style.display = 'block';
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<i class="fa-solid fa-paper-plane"></i> Invia richiesta';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -104,6 +104,7 @@ $controllerMap = [
|
||||
'whistleblowing'=> 'WhistleblowingController',
|
||||
'normative' => 'NormativeController',
|
||||
'cross-analysis' => 'CrossAnalysisController',
|
||||
'contact' => 'ContactController',
|
||||
];
|
||||
|
||||
if (!isset($controllerMap[$controllerName])) {
|
||||
@ -369,6 +370,11 @@ $actionMap = [
|
||||
'GET:history' => 'history',
|
||||
'GET:portfolio' => 'portfolio',
|
||||
],
|
||||
|
||||
// ── ContactController (lead / richiesta invito) ──
|
||||
'contact' => [
|
||||
'POST:requestInvite' => 'requestInvite',
|
||||
],
|
||||
];
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
Loading…
Reference in New Issue
Block a user