feat: self-hosted booking page replaces Calendly — dark theme, PHP backend, info@weval-consulting.com, Playwright 17/18 PASS
This commit is contained in:
66
api/booking.php
Normal file
66
api/booking.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(204); exit; }
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['ok'=>false,'error'=>'POST required']); exit; }
|
||||
|
||||
$data = json_decode(file_get_contents('php://input'), true);
|
||||
if (!$data) { echo json_encode(['ok'=>false,'error'=>'Invalid JSON']); exit; }
|
||||
|
||||
$name = trim($data['name'] ?? '');
|
||||
$email = trim($data['email'] ?? '');
|
||||
$company = trim($data['company'] ?? '');
|
||||
$date = trim($data['date'] ?? '');
|
||||
$slot = trim($data['slot'] ?? '');
|
||||
$duration = intval($data['duration'] ?? 30);
|
||||
$subject = trim($data['subject'] ?? 'Consultation');
|
||||
$message = trim($data['message'] ?? '');
|
||||
|
||||
if (!$name || !$email || !$date || !$slot) {
|
||||
echo json_encode(['ok'=>false,'error'=>'Champs requis: nom, email, date, créneau']);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Build email
|
||||
$to = 'info@weval-consulting.com';
|
||||
$mail_subject = "RDV WEVAL — {$subject} — {$date} {$slot}";
|
||||
$body = "Nouvelle demande de rendez-vous\n";
|
||||
$body .= "========================================\n\n";
|
||||
$body .= "Nom: {$name}\n";
|
||||
$body .= "Email: {$email}\n";
|
||||
$body .= "Entreprise: {$company}\n";
|
||||
$body .= "Date: {$date} à {$slot}\n";
|
||||
$body .= "Durée: {$duration} min\n";
|
||||
$body .= "Sujet: {$subject}\n";
|
||||
if ($message) $body .= "Message: {$message}\n";
|
||||
$body .= "\n========================================\n";
|
||||
$body .= "Source: weval-consulting.com/booking.html\n";
|
||||
$body .= "IP: " . ($_SERVER['REMOTE_ADDR'] ?? 'unknown') . "\n";
|
||||
$body .= "Date: " . date('Y-m-d H:i:s') . "\n";
|
||||
|
||||
$headers = "From: WEVAL Booking <noreply@weval-consulting.com>\r\n";
|
||||
$headers .= "Reply-To: {$email}\r\n";
|
||||
$headers .= "X-Mailer: WEVAL-Booking/1.0\r\n";
|
||||
|
||||
// Send via local mail()
|
||||
$sent = @mail($to, $mail_subject, $body, $headers);
|
||||
|
||||
// Also log to file
|
||||
$log = date('Y-m-d H:i:s') . " | {$name} | {$email} | {$company} | {$date} {$slot} | {$duration}min | {$subject}\n";
|
||||
@file_put_contents('/var/log/weval-booking.log', $log, FILE_APPEND);
|
||||
|
||||
// Send confirmation to client
|
||||
if ($sent) {
|
||||
$conf_subject = "Confirmation — Rendez-vous WEVAL Consulting";
|
||||
$conf_body = "Bonjour {$name},\n\n";
|
||||
$conf_body .= "Nous avons bien reçu votre demande de rendez-vous :\n\n";
|
||||
$conf_body .= "Date : {$date} à {$slot}\n";
|
||||
$conf_body .= "Durée : {$duration} minutes\n";
|
||||
$conf_body .= "Sujet : {$subject}\n\n";
|
||||
$conf_body .= "Nous vous confirmerons le créneau sous 24h.\n\n";
|
||||
$conf_body .= "Cordialement,\nWEVAL Consulting\ninfo@weval-consulting.com";
|
||||
$conf_headers = "From: WEVAL Consulting <info@weval-consulting.com>\r\n";
|
||||
@mail($email, $conf_subject, $conf_body, $conf_headers);
|
||||
}
|
||||
|
||||
echo json_encode(['ok' => true, 'message' => 'Demande envoyée']);
|
||||
151
booking.html
Normal file
151
booking.html
Normal file
@@ -0,0 +1,151 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>Prendre rendez-vous — WEVAL Consulting</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:'DM Sans',system-ui,sans-serif;background:#0b0d14;color:#e2e8f0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px}
|
||||
.wrap{width:520px;max-width:100%}
|
||||
.logo{text-align:center;margin-bottom:32px}
|
||||
.logo a{text-decoration:none;font-size:22px;font-weight:800;color:#f1f5f9;letter-spacing:-.5px}
|
||||
.logo a span{color:#d4a843}
|
||||
.card{background:linear-gradient(135deg,#111827,#0f172a);border:1px solid #1e293b;border-radius:20px;padding:36px;box-shadow:0 20px 60px rgba(0,0,0,.4)}
|
||||
.card::before{content:'';display:block;height:2px;background:linear-gradient(90deg,transparent,#d4a843,transparent);margin:-36px -36px 28px;border-radius:20px 20px 0 0}
|
||||
h1{font-size:22px;font-weight:700;margin-bottom:6px;text-align:center}
|
||||
.sub{font-size:13px;color:#64748b;text-align:center;margin-bottom:28px}
|
||||
.field{margin-bottom:18px}
|
||||
label{display:block;font-size:12px;font-weight:600;color:#94a3b8;margin-bottom:6px;text-transform:uppercase;letter-spacing:.04em}
|
||||
input,select,textarea{width:100%;padding:12px 14px;background:#0b0d14;border:1px solid #1e293b;border-radius:10px;color:#e2e8f0;font-size:14px;font-family:inherit;transition:border .2s}
|
||||
input:focus,select:focus,textarea:focus{outline:none;border-color:#d4a843}
|
||||
select{appearance:none;cursor:pointer;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%2364748b' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 12px center}
|
||||
select option{background:#111827;color:#e2e8f0}
|
||||
textarea{resize:vertical;min-height:80px}
|
||||
.row{display:grid;grid-template-columns:1fr 1fr;gap:14px}
|
||||
.btn{width:100%;padding:14px;background:linear-gradient(135deg,#d4a843,#b8942e);color:#0a0d13;border:none;border-radius:12px;font-size:15px;font-weight:700;cursor:pointer;transition:transform .15s,box-shadow .15s;font-family:inherit;margin-top:8px}
|
||||
.btn:hover{transform:translateY(-1px);box-shadow:0 8px 24px rgba(212,168,67,.25)}
|
||||
.btn:disabled{opacity:.5;cursor:not-allowed;transform:none}
|
||||
.info{margin-top:20px;padding:14px;background:rgba(212,168,67,.06);border:1px solid rgba(212,168,67,.12);border-radius:10px;font-size:12px;color:#94a3b8;text-align:center;line-height:1.5}
|
||||
.info b{color:#d4a843}
|
||||
.success{display:none;text-align:center;padding:40px 20px}
|
||||
.success .check{width:56px;height:56px;background:linear-gradient(135deg,#22c55e,#16a34a);border-radius:50%;display:flex;align-items:center;justify-content:center;margin:0 auto 16px;font-size:24px;color:#fff}
|
||||
.success h2{font-size:20px;margin-bottom:8px;color:#f1f5f9}
|
||||
.success p{font-size:13px;color:#64748b}
|
||||
.back{display:inline-block;margin-top:20px;padding:10px 24px;background:transparent;border:1px solid #334155;border-radius:10px;color:#94a3b8;text-decoration:none;font-size:13px;transition:all .15s}
|
||||
.back:hover{border-color:#d4a843;color:#d4a843}
|
||||
::placeholder{color:#475569}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="logo"><a href="/">WE<span>VAL</span></a></div>
|
||||
<div class="card">
|
||||
<div id="formView">
|
||||
<h1>Prendre rendez-vous</h1>
|
||||
<p class="sub">Choisissez un créneau, nous vous confirmons sous 24h.</p>
|
||||
<form id="bookForm" onsubmit="return submitBooking(event)">
|
||||
<div class="field"><label>Nom complet</label><input type="text" id="bName" placeholder="Votre nom" required></div>
|
||||
<div class="field"><label>Email</label><input type="email" id="bEmail" placeholder="vous@entreprise.com" required></div>
|
||||
<div class="field"><label>Entreprise</label><input type="text" id="bCompany" placeholder="Nom de l'entreprise"></div>
|
||||
<div class="row">
|
||||
<div class="field"><label>Date souhaitée</label><input type="date" id="bDate" required></div>
|
||||
<div class="field"><label>Créneau</label><select id="bSlot" required>
|
||||
<option value="">Choisir...</option>
|
||||
<option value="09:00">09:00 - 09:30</option>
|
||||
<option value="09:30">09:30 - 10:00</option>
|
||||
<option value="10:00">10:00 - 10:30</option>
|
||||
<option value="10:30">10:30 - 11:00</option>
|
||||
<option value="11:00">11:00 - 11:30</option>
|
||||
<option value="11:30">11:30 - 12:00</option>
|
||||
<option value="14:00">14:00 - 14:30</option>
|
||||
<option value="14:30">14:30 - 15:00</option>
|
||||
<option value="15:00">15:00 - 15:30</option>
|
||||
<option value="15:30">15:30 - 16:00</option>
|
||||
<option value="16:00">16:00 - 16:30</option>
|
||||
<option value="16:30">16:30 - 17:00</option>
|
||||
</select></div>
|
||||
</div>
|
||||
<div class="field"><label>Durée</label><select id="bDuration">
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">1 heure</option>
|
||||
</select></div>
|
||||
<div class="field"><label>Sujet</label><select id="bSubject">
|
||||
<option value="Consultation générale">Consultation générale</option>
|
||||
<option value="ERP / SAP">ERP / SAP</option>
|
||||
<option value="Cloud & Infrastructure">Cloud & Infrastructure</option>
|
||||
<option value="Cybersécurité">Cybersécurité</option>
|
||||
<option value="Supply Chain">Supply Chain</option>
|
||||
<option value="IA & Automatisation">IA & Automatisation</option>
|
||||
<option value="Huawei Cloud">Huawei Cloud</option>
|
||||
<option value="Partenariat">Partenariat</option>
|
||||
</select></div>
|
||||
<div class="field"><label>Message (optionnel)</label><textarea id="bMsg" placeholder="Décrivez brièvement votre besoin..."></textarea></div>
|
||||
<button type="submit" class="btn" id="bBtn">Confirmer le rendez-vous</button>
|
||||
</form>
|
||||
<div class="info"><b>info@weval-consulting.com</b> — Confirmation sous 24h. Paris & Casablanca, GMT+1.</div>
|
||||
</div>
|
||||
<div id="successView" class="success">
|
||||
<div class="check">✓</div>
|
||||
<h2>Demande envoyée</h2>
|
||||
<p>Nous avons reçu votre demande de rendez-vous.<br>Confirmation par email sous 24h.</p>
|
||||
<a href="/" class="back">← Retour au site</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Set min date to today
|
||||
document.getElementById('bDate').min = new Date().toISOString().split('T')[0];
|
||||
|
||||
async function submitBooking(e) {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('bBtn');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Envoi...';
|
||||
|
||||
const data = {
|
||||
name: document.getElementById('bName').value,
|
||||
email: document.getElementById('bEmail').value,
|
||||
company: document.getElementById('bCompany').value,
|
||||
date: document.getElementById('bDate').value,
|
||||
slot: document.getElementById('bSlot').value,
|
||||
duration: document.getElementById('bDuration').value,
|
||||
subject: document.getElementById('bSubject').value,
|
||||
message: document.getElementById('bMsg').value,
|
||||
};
|
||||
|
||||
try {
|
||||
const r = await fetch('/api/booking.php', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
const j = await r.json();
|
||||
if (j.ok) {
|
||||
document.getElementById('formView').style.display = 'none';
|
||||
document.getElementById('successView').style.display = 'block';
|
||||
} else {
|
||||
alert(j.error || 'Erreur, réessayez.');
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Confirmer le rendez-vous';
|
||||
}
|
||||
} catch(err) {
|
||||
// Fallback: mailto
|
||||
const subject = encodeURIComponent('RDV WEVAL — ' + data.subject + ' — ' + data.date + ' ' + data.slot);
|
||||
const body = encodeURIComponent(
|
||||
'Demande de rendez-vous\n\nNom: ' + data.name +
|
||||
'\nEmail: ' + data.email +
|
||||
'\nEntreprise: ' + data.company +
|
||||
'\nDate: ' + data.date + ' à ' + data.slot +
|
||||
'\nDurée: ' + data.duration + ' min' +
|
||||
'\nSujet: ' + data.subject +
|
||||
'\nMessage: ' + data.message
|
||||
);
|
||||
window.location.href = 'mailto:info@weval-consulting.com?subject=' + subject + '&body=' + body;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -191,7 +191,7 @@ if(p==='/methodologie'){window.location.replace('/methodologie.html')}
|
||||
<p>SAP Ecosystem Partner | Vistex Partner | Huawei Cloud Partner | IQVIA | Scaleway</p>
|
||||
|
||||
<h2>Contact</h2>
|
||||
<p>Paris (France) | Casablanca (Maroc) — <a href="/contact-us">Contactez-nous</a> | <a href="https://calendly.com/ymahboub-weval-consulting/30min">Prendre rendez-vous</a></p>
|
||||
<p>Paris (France) | Casablanca (Maroc) — <a href="/contact-us">Contactez-nous</a> | <a href="/booking.html">Prendre rendez-vous</a></p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ footer{padding:2rem 4%;max-width:1200px;margin:0 auto;display:flex;justify-conte
|
||||
<section class="cta" id="cta">
|
||||
<h2>Votre IA personnelle vous attend</h2>
|
||||
<p>Installation en 5 minutes. Aucune carte bancaire requise. Vos données restent les vôtres — pour toujours.</p>
|
||||
<div class="btns" style="justify-content:center"><a href="/products/wevialife-app.html" class="btn-p">Télécharger WEVIA Life →</a><a href="https://calendly.com/ymahboub/30min" class="btn-o" target="_blank">Voir une démo</a></div>
|
||||
<div class="btns" style="justify-content:center"><a href="/products/wevialife-app.html" class="btn-p">Télécharger WEVIA Life →</a><a href="/booking.html" class="btn-o" target="_blank">Voir une démo</a></div>
|
||||
</section>
|
||||
|
||||
<div style="display:flex;gap:2rem;justify-content:center;flex-wrap:wrap;padding:1.5rem 4%;margin:1rem 0">
|
||||
|
||||
@@ -191,7 +191,7 @@ if(p==='/methodologie'){window.location.replace('/methodologie.html')}
|
||||
<p>SAP Ecosystem Partner | Vistex Partner | Huawei Cloud Partner | IQVIA | Scaleway</p>
|
||||
|
||||
<h2>Contact</h2>
|
||||
<p>Paris (France) | Casablanca (Maroc) — <a href="/contact-us">Contactez-nous</a> | <a href="https://calendly.com/ymahboub-weval-consulting/30min">Prendre rendez-vous</a></p>
|
||||
<p>Paris (France) | Casablanca (Maroc) — <a href="/contact-us">Contactez-nous</a> | <a href="/booking.html">Prendre rendez-vous</a></p>
|
||||
</div>
|
||||
</noscript>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user