| Current Path : /home/happyrenas/find.myreco.online/public/ |
Linux webd005.cluster105.gra.hosting.ovh.net 5.15.206-ovh-vps-grsec-zfs-classid #1 SMP Fri May 15 02:41:25 UTC 2026 x86_64 |
| Current File : /home/happyrenas/find.myreco.online/public/accept.php |
<?php
declare(strict_types=1);
/**
* accept.php
* Page acceptation (édition + preview + queue) — ezSQL, sans prepare
*/
session_start();
require_once __DIR__ . '/../configuration.php';
require_once __DIR__ . '/../includes/fonctions.php';
/* =========================================================
Helpers (fallback)
========================================================= */
if (!function_exists('json_out')) {
function json_out(array $payload, int $code = 200): void {
http_response_code($code);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($payload, JSON_UNESCAPED_UNICODE);
exit;
}
}
if (!function_exists('h')) {
function h($s): string { return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8'); }
}
/* =========================================================
Inputs lien signé
- On accepte GET (arrivée depuis dispo.php) et POST (actions preview/submit)
========================================================= */
$t = (string)($_GET['t'] ?? ($_POST['t'] ?? ''));
$sig = (string)($_GET['sig'] ?? ($_POST['sig'] ?? ''));
/**
* IMPORTANT:
* La signature doit être cohérente avec dispo.php :
* dispo.php valide via make_sig($a, $t)
* donc ici pour accept => make_sig('accept', $t)
*/
if (!preg_match('/^[a-f0-9]{32}$/', $t)) {
http_response_code(400);
echo "Lien invalide.";
exit;
}
if (!hash_equals(make_sig('accept', $t), $sig)) {
http_response_code(403);
echo "Signature invalide.";
exit;
}
/* =========================================================
Récupération demande d'origine (modele_id = 1)
========================================================= */
$row = $db->get_row("
SELECT *
FROM email_queue
WHERE action_token = '" . $db->escape($t) . "'
AND modele_id = 1
LIMIT 1
");
if (!$row) {
http_response_code(404);
echo "Demande introuvable.";
exit;
}
/* =========================================================
Context JSON
========================================================= */
$ctx = [];
if (!empty($row->context_json)) {
$tmp = json_decode((string)$row->context_json, true);
if (is_array($tmp)) $ctx = $tmp;
}
/* =========================================================
Pull structure (selon ton context_json)
========================================================= */
$token = $ctx['token'] ?? ($row->token ?? '');
$hebergement_nom = (string)($ctx['hebergement']['name'] ?? '');
$hebergement_ville = (string)($ctx['hebergement']['ville'] ?? '');
$hebergement_country = (string)($ctx['hebergement']['country'] ?? '');
$site = (string)($ctx['hebergement']['site'] ?? '');
$site_reservation = (string)($ctx['hebergement']['site_reservation'] ?? '');
$email_reservation = (string)($ctx['hebergement']['email_reservation'] ?? '');
$prenom = (string)($ctx['form']['prenom'] ?? ''); // souvent vide si tu n’as pas ce champ côté formulaire
$nom = (string)($ctx['form']['nom'] ?? '');
$email_voyageur = (string)($ctx['form']['email'] ?? '');
$date_debut = (string)($ctx['form']['date_debut'] ?? '');
$date_fin = (string)($ctx['form']['date_fin'] ?? '');
$guests = (string)($ctx['form']['voyageur_nombre'] ?? '');
/* nuits : supporte plusieurs clés + fallback */
$nuits = $ctx['form']['nuits'] ?? ($ctx['form']['nuit_nombre'] ?? '');
if ($nuits === '' && $date_debut && $date_fin) {
try {
$d1 = new DateTimeImmutable($date_debut);
$d2 = new DateTimeImmutable($date_fin);
$nuits = (int)$d1->diff($d2)->days;
if ($d2 < $d1) $nuits = 0;
} catch (Throwable $e) {
$nuits = 0;
}
}
$response_status = (string)($row->response_status ?? 'pending');
/* =========================================================
POST: preview / submit
========================================================= */
if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
if (ob_get_level()) { @ob_end_clean(); }
$action = (string)($_POST['action'] ?? '');
// Champs modifiables
$date_debut_in = trim((string)($_POST['date_debut'] ?? ''));
$date_fin_in = trim((string)($_POST['date_fin'] ?? ''));
$guests_in = trim((string)($_POST['voyageur_nombre'] ?? ''));
$nuits_in = trim((string)($_POST['nuits'] ?? ''));
$tarif_in = trim((string)($_POST['tarif'] ?? ''));
$msg_host_in = trim((string)($_POST['message_proprio'] ?? ''));
$site_resa_in = trim((string)($_POST['site_reservation'] ?? ''));
$email_resa_in = trim((string)($_POST['email_reservation'] ?? ''));
$site_in = trim((string)($_POST['site'] ?? ''));
/* -------------------------
Validation formats
-------------------------- */
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_debut_in) || !preg_match('/^\d{4}-\d{2}-\d{2}$/', $date_fin_in)) {
json_out(['ok'=>false,'error'=>'Format de date invalide']);
}
if (!ctype_digit($guests_in) || (int)$guests_in < 1 || (int)$guests_in > 99) {
json_out(['ok'=>false,'error'=>'Nombre de personnes invalide']);
}
if ($nuits_in === '' || !ctype_digit($nuits_in) || (int)$nuits_in < 0 || (int)$nuits_in > 365) {
json_out(['ok'=>false,'error'=>'Nombre de nuités invalide']);
}
/* Validation réelle des dates + règles métier */
$tz = new DateTimeZone('Europe/Paris');
$dtDebut = DateTimeImmutable::createFromFormat('!Y-m-d', $date_debut_in, $tz);
$dtFin = DateTimeImmutable::createFromFormat('!Y-m-d', $date_fin_in, $tz);
$errDeb = DateTimeImmutable::getLastErrors();
if (!$dtDebut || ($errDeb['warning_count'] ?? 0) || ($errDeb['error_count'] ?? 0)) {
json_out(['ok'=>false,'error'=>'Date de début invalide']);
}
$errFin = DateTimeImmutable::getLastErrors();
if (!$dtFin || ($errFin['warning_count'] ?? 0) || ($errFin['error_count'] ?? 0)) {
json_out(['ok'=>false,'error'=>'Date de fin invalide']);
}
$today = new DateTimeImmutable('today', $tz);
if ($dtDebut < $today || $dtFin < $today) {
json_out(['ok'=>false,'error'=>"Merci de choisir des dates à partir d’aujourd’hui."]);
}
if ($dtDebut > $dtFin) {
json_out(['ok'=>false,'error'=>"La date de début doit être antérieure ou égale à la date de fin."]);
}
/* -------------------------
Vars template MODELE 3 (email voyageur)
(on garde les dates en Y-m-d comme dans ton code)
-------------------------- */
$vars = [
'hebergement_nom' => h($hebergement_nom),
'hebergement_ville' => h(trim($hebergement_ville . ($hebergement_country ? ', '.$hebergement_country : ''))),
'date_debut' => h(date_fr_auto($date_debut_in)),
'date_fin' => h(date_fr_auto($date_fin_in)),
'voyageur_nombre' => h($guests_in),
'nuits' => h($nuits_in),
'tarif' => h($tarif_in),
'site' => h($site_in),
'site_reservation' => h($site_resa_in),
'email_reservation' => h($email_resa_in),
'message_proprio' => nl2br(h($msg_host_in)),
];
/* =========================================================
PREVIEW
========================================================= */
if ($action === 'preview') {
[$ok, $html] = render_modele_html(3, $vars);
if (!$ok) json_out(['ok'=>false,'error'=>$html]);
$dd_fr = date_fr_auto($date_debut_in);
$df_fr = date_fr_auto($date_fin_in);
json_out([
'ok' => true,
'html' => $html,
'subject' => "Disponibilité confirmée — ".$hebergement_nom." — ".$dd_fr." → ".$df_fr,
'to' => $email_voyageur
]);
}
/* =========================================================
SUBMIT
========================================================= */
if ($action === 'submit') {
if (!filter_var($email_voyageur, FILTER_VALIDATE_EMAIL)) {
json_out(['ok'=>false,'error'=>"Email voyageur invalide"]);
}
// rendu final mail (avant transaction)
[$ok, $html] = render_modele_html(3, $vars);
if (!$ok) json_out(['ok'=>false,'error'=>$html]);
// Enrichit le context_json avec l'acceptation
$ctx['acceptation'] = [
'date_debut' => $date_debut_in,
'date_fin' => $date_fin_in,
'voyageur_nombre' => (int)$guests_in,
'nuits' => (int)$nuits_in,
'tarif' => $tarif_in,
'message_proprio' => $msg_host_in,
'site' => $site_in,
'site_reservation' => $site_resa_in,
'email_reservation' => $email_resa_in,
'validated_at' => date('c'),
];
$ctx_json_new = json_encode($ctx, JSON_UNESCAPED_UNICODE);
$request_uid = (string)($row->request_uid ?? '');
if ($request_uid === '') $request_uid = bin2hex(random_bytes(16));
$tok = (string)($row->token ?? $token);
$now = date('Y-m-d H:i:s');
$ip = (string)($_SERVER['REMOTE_ADDR'] ?? '');
$expediteur = 'contact@myreco.online';
/* -------------------------
Transaction (anti-double + cohérence)
-------------------------- */
$mysqli = $db->dbh ?? null;
if (!($mysqli instanceof mysqli)) {
json_out(['ok'=>false,'error'=>'Connexion mysqli introuvable (transaction impossible)']);
}
$mysqli->begin_transaction();
// 1) verrou logique : on “prend” la demande si encore pending
$okUpd = $db->query("
UPDATE email_queue
SET response_status='accepted',
response_at='" . $db->escape($now) . "',
response_ip='" . $db->escape($ip) . "',
context_json='" . $db->escape($ctx_json_new) . "'
WHERE id=" . (int)$row->id . "
AND (response_status IS NULL OR response_status='pending')
LIMIT 1
");
// Si déjà traité, on rollback
$rowsAffected = property_exists($db, 'rows_affected') ? (int)$db->rows_affected : null;
if (!$okUpd || ($rowsAffected !== null && $rowsAffected < 1)) {
$mysqli = $db->dbh ?? null;
if (!($mysqli instanceof mysqli)) {
json_out(['ok'=>false,'error'=>'Connexion mysqli introuvable (transaction impossible)']);
}
$mysqli->begin_transaction();
json_out(['ok'=>false,'error'=>'Réponse déjà enregistrée.']);
}
// 2) Insert email voyageur (modele_id = 3)
$action_token2 = bin2hex(random_bytes(16));
$objet = "Disponibilité confirmée — $hebergement_nom - " . date_fr_auto($date_debut_in) . " → " . date_fr_auto($date_fin_in);
$okIns = $db->query("
INSERT INTO email_queue
(action_token, request_uid, token, modele_id, expediteur, destinataire_principal, destinataire_copie,
objet, message, context_json, status, error_message, retries, last_attempt, next_attempt, created_at)
VALUES
(
'" . $db->escape($action_token2) . "',
'" . $db->escape($request_uid) . "',
'" . $db->escape($tok) . "',
3,
'" . $db->escape($expediteur) . "',
'" . $db->escape($email_voyageur) . "',
'',
'" . $db->escape($objet) . "',
'" . $db->escape($html) . "',
'" . $db->escape($ctx_json_new) . "',
'queued',
NULL,
0,
NULL,
'" . $db->escape($now) . "',
NOW()
)
");
if (!$okIns) {
$mysqli->rollback();
json_out(['ok'=>false,'error'=>"Erreur DB (insert queue voyageur)"]);
}
$newId = (int)$db->insert_id;
// 3) ACK hébergeur (accusé d’envoi OUI) — si email présent
if (!filter_var($email_resa_in, FILTER_VALIDATE_EMAIL)) {
$email_resa_in="pasdemail@myreco.online";
}
$tplPathAck = __DIR__ . '/../matrice/modele_accuse_acceptation.html';
$tplAck = is_file($tplPathAck) ? (string)file_get_contents($tplPathAck) : '';
if ($tplAck !== '') {
$fmtDateFr = function(string $ymd): string {
if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $ymd)) return $ymd;
$dt = DateTimeImmutable::createFromFormat('!Y-m-d', $ymd);
return $dt ? $dt->format('d/m/Y') : $ymd;
};
$unsubscribe_url = APP_BASE_URL . '/public/unsubscribe.php?email=' . urlencode($email_resa_in);
$sentDt = new DateTimeImmutable($now); // moment du clic "Envoyer"
$sent_date = $sentDt->format('d/m/Y');
$sent_time = $sentDt->format('H:i');
$varsAck = [
'nom_du_client' => h(trim($prenom.' '.$nom)),
'email_voyageur' => h($email_voyageur),
'nom_commercial' => h($hebergement_nom),
'date_start' => h($fmtDateFr($date_debut_in)),
'date_end' => h($fmtDateFr($date_fin_in)),
'guests' => h($guests_in),
'nombre_nuite' => h($nuits_in),
'tarif' => h($tarif_in),
'site_reservation' => h($site_resa_in),
'email_reservation'=> h($email_resa_in),
'unsubscribe_url' => h($unsubscribe_url),
'sent_date' => h($sent_date),
'sent_time' => h($sent_time),
];
$tplAck = tpl_if_blocks($tplAck, $varsAck);
$ackHtml = tpl_vars($tplAck, $varsAck);
$action_token3 = bin2hex(random_bytes(16));
$objetAck = "[MyReco] Accusé d'envoi — [DISPO:OUI] — ".$hebergement_nom ." du " . date_fr_auto($date_debut) . " au " . date_fr_auto($date_fin)." - ".$voyageur_nombre." pers.";
$okAck = $db->query("
INSERT INTO email_queue
(action_token, request_uid, token, modele_id, expediteur, destinataire_principal, destinataire_copie,
objet, message, context_json, status, error_message, retries, last_attempt, next_attempt, created_at)
VALUES
(
'" . $db->escape($action_token3) . "',
'" . $db->escape($request_uid) . "',
'" . $db->escape($tok) . "',
0,
'" . $db->escape($expediteur) . "',
'" . $db->escape($email_resa_in) . "',
'',
'" . $db->escape($objetAck) . "',
'" . $db->escape($ackHtml) . "',
'" . $db->escape($ctx_json_new) . "',
'queued',
NULL,
0,
NULL,
'" . $db->escape($now) . "',
NOW()
)
");
if (!$okAck) {
$mysqli->rollback();
json_out(['ok'=>false,'error'=>"Erreur DB (insert ACK hôte)"]);
}
}
//}
$mysqli->commit();
json_out(['ok'=>true,'queued_id'=>$newId]);
}
json_out(['ok'=>false,'error'=>'Action invalide']);
}
/* =========================================================
GET: page UI
========================================================= */
header('Content-Type: text/html; charset=utf-8');
// tarif total pré-rempli : priorité à une acceptation déjà enregistrée
$tarif_prefill = (string)($ctx['acceptation']['tarif'] ?? '');
if ($tarif_prefill === '') {
$tarif_nuit = (string)($ctx['hebergement']['tarif_nuit'] ?? '');
$tarif_nuit = str_replace(['€',' '], '', $tarif_nuit);
$tarif_nuit = str_replace(',', '.', $tarif_nuit);
// si tarif_nuit est numérique => calc total
if ($tarif_nuit !== '' && preg_match('/^\d+(\.\d{1,2})?$/', $tarif_nuit)) {
$n = (int)$nuits;
// $g = (int)$guests; // (non utilisé pour le calcul du tarif)
if ($n > 0) {
$total = (float)$tarif_nuit * $n;
// format FR simple
$tarif_prefill = number_format($total, 2, ',', ' ');
}
}
}
$message_host_prefill = $ctx['acceptation']['message_proprio'] ?? '';
$date_debut_fr = $date_debut ? date_fr_auto($date_debut) : '';
$date_fin_fr = $date_fin ? date_fr_auto($date_fin) : '';
?>
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Répondre à la demande</title>
<style>
:root{
--bg:#f6f7fb;
--card:#ffffff;
--text:#111827;
--muted:#6b7280;
--line:#e5e7eb;
--accent:#2907f0;
--soft:#f3f4f6;
--radius:18px;
/* Marron MyReco */
--brown:#a2907f;
--brown2:#8f7f70;
}
*{box-sizing:border-box}
body{
margin:0;
font-family:ui-sans-serif,system-ui,-apple-system,Segoe UI,Roboto,Arial;
background:var(--bg);
color:var(--text);
padding:28px 16px;
}
.wrap{max-width:1180px;margin:0 auto}
h1{font-size:20px;margin:0}
.sub{color:var(--muted);font-size:13px;margin-top:6px;line-height:1.4}
.banner{
margin-top:14px;
padding:12px 14px;
border-radius:14px;
border:1px solid var(--line);
background:#fff;
color:#111827;
box-shadow:0 10px 26px rgba(17,24,39,.06);
}
.banner.warn{background:#fff7ed;border-color:#fed7aa}
.banner.ok{background:#ecfdf3;border-color:#bbf7d0}
.global{
margin-top:14px;
background:var(--card);
border:1px solid var(--line);
border-radius:var(--radius);
box-shadow:0 10px 26px rgba(17,24,39,.08);
overflow:hidden;
}
.globalGrid{
display:grid;
grid-template-columns: 1.05fr .95fr;
gap:0;
min-height:560px;
}
@media (max-width: 980px){
.globalGrid{grid-template-columns:1fr}
}
.colLeft{
padding:18px;
border-right:1px solid var(--line);
background:linear-gradient(180deg, rgba(41,7,240,.03), transparent 40%);
}
@media (max-width: 980px){
.colLeft{border-right:0;border-bottom:1px solid var(--line)}
}
.colRight{
padding:18px;
background:#fbfbff;
}
.leftGrid{display:grid;gap:14px}
.subcard{
background:#fff;
border:1px solid var(--line);
border-radius:16px;
padding:16px;
}
.title{
display:flex;
justify-content:space-between;
align-items:center;
gap:12px;
margin-bottom:10px;
}
.title h2{font-size:16px;margin:0}
.badge{
font-size:12px;
color:var(--muted);
background:var(--soft);
border:1px solid var(--line);
padding:6px 10px;
border-radius:999px;
white-space:nowrap;
}
.subcardBrown{
border-radius:16px;
padding:14px;
background:linear-gradient(180deg, var(--brown), var(--brown2));
border:1px solid rgba(0,0,0,.08);
color:#fff;
}
.titleBrown{
display:flex;
justify-content:space-between;
align-items:center;
gap:12px;
margin-bottom:10px;
}
.titleBrown h2{font-size:15px;margin:0;color:#fff}
.badgeBrown{
font-size:12px;
color:rgba(255,255,255,.92);
background:rgba(255,255,255,.12);
border:1px solid rgba(255,255,255,.22);
padding:6px 10px;
border-radius:999px;
white-space:nowrap;
}
.compactGrid{
display:grid;
grid-template-columns: 1fr 1fr;
gap:10px;
}
@media (max-width: 520px){
.compactGrid{grid-template-columns:1fr}
}
.chip{
padding:10px 10px;
border-radius:12px;
background:#ffffff;
border:1px solid rgba(0,0,0,.12);
display:flex;
justify-content:space-between;
gap:10px;
align-items:flex-start;
color:#111827;
}
.chip b{
font-size:12.5px;
letter-spacing:.2px;
color:#111827;
flex:0 0 auto;
}
.chip span{
font-size:12.5px;
color:#374151;
text-align:right;
line-height:1.35;
}
.compactWide{grid-column:1 / -1}
label{display:block;font-size:13px;margin:10px 0 6px}
input{
width:100%;
padding:12px;
border:1px solid var(--line);
border-radius:12px;
font-size:14px;
outline:none;
background:#fff;
}
input:focus{
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(41,7,240,.12);
}
textarea{
width:100%;
padding:12px;
border:1px solid var(--line);
border-radius:12px;
font-size:14px;
outline:none;
background:#fff;
min-height:170px;
resize:vertical;
}
textarea:focus{
border-color:var(--accent);
box-shadow:0 0 0 3px rgba(41,7,240,.12);
}
.row2{display:grid;grid-template-columns:1fr 1fr;gap:12px}
@media (max-width: 520px){ .row2{grid-template-columns:1fr} }
.info{
padding:12px;
border:1px dashed #c7c9d3;
border-radius:14px;
background:#fbfbff;
color:var(--muted);
font-size:13px;
line-height:1.45;
margin-bottom:10px;
}
a{color:var(--accent);text-decoration:none}
a:hover{text-decoration:underline}
.btnRow{
display:flex;
align-items:center;
gap:12px;
flex-wrap:wrap;
margin-top:12px;
}
.btn{
border:0;
border-radius:12px;
padding:12px 14px;
font-weight:800;
cursor:pointer;
font-size:14px
}
.primary{background:#3B82F6;color:#fff}
.secondary{background:#eef2ff;color:#1f2937}
.btn:disabled{opacity:.55;cursor:not-allowed}
.btnNote{
color:var(--muted);
font-size:12px;
line-height:1.35;
max-width:360px;
}
.previewWrap{
height:100%;
display:flex;
flex-direction:column;
gap:12px;
}
.previewHeader{
display:flex;
justify-content:space-between;
align-items:center;
gap:12px;
background:#fff;
border:1px solid var(--line);
border-radius:16px;
padding:14px;
}
.previewHeader h2{font-size:16px;margin:0}
.preview{
flex:1;
background:#0b1020;
color:#e5e7eb;
border-radius:16px;
padding:16px;
border:1px solid rgba(255,255,255,.12);
font-size:13px;
line-height:1.6;
overflow:auto;
min-height:320px;
}
.meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}
.tag{
background:rgba(255,255,255,.08);
border:1px solid rgba(255,255,255,.12);
padding:6px 10px;
border-radius:999px;
font-size:12px;
}
.mini{color:var(--muted);font-size:12px}
.iframe{
width:100%;
height:740px;
border:1px solid rgba(255,255,255,.16);
border-radius:12px;
background:#fff;
display:block;
margin-top:12px;
}
.inputBrown{
border:4px solid #a2907f;
}
.inputBrown:focus{
border-color:#8f7f70;
box-shadow:0 0 0 3px rgba(162,144,127,.25);
}
.btn-mini{
border: 1px solid rgba(0,0,0,.15);
background: #fff;
color: #111827;
border-radius: 999px;
padding: 4px 10px;
font-size: 12px;
line-height: 1.2;
cursor: pointer;
user-select: none;
}
.btn-mini:hover{
background: rgba(0,0,0,.04);
}
.btn-mini:active{
transform: translateY(1px);
}
</style>
</head>
<body>
<div class="wrap">
<?php if ($response_status !== 'pending'): ?>
<div class="banner ok">
<?if ($response_status=='accepted') {$response_affiche=" acceptée";}?> Demande <b><?=h($response_affiche);?></b>.
<?exit;?>
</div>
<?php else: ?>
<div class="banner warn">
<!--Modifier au besoin, cliquer sur <b>Aperçu du mail</b>, puis <b>Envoyer</b>.-->
</div>
<?php endif; ?>
<section class="global" aria-label="formulaire">
<div class="globalGrid">
<div class="colLeft">
<div class="leftGrid">
<div class="subcardBrown">
<div class="titleBrown">
<h2>Rappel de la demande</h2>
<span class="badgeBrown">pré-rempli</span>
</div>
<div class="compactGrid">
<div class="chip compactWide">
<b>Période</b>
<span id="periodeText"><?=h($date_debut)?> → <?=h($date_fin)?></span>
</div>
<div class="chip compactWide">
<b>Hébergement concerné</b>
<span><?=h($hebergement_nom)?></span>
</div>
<div class="chip">
<b>Voyageur</b>
<span><?=h(trim($prenom.' '.$nom))?></span>
</div>
<div class="chip">
<b>Personnes</b>
<span id="guestsText"><?=h($guests)?></span>
</div>
<div class="chip compactWide">
<b>Nuités</b>
<span id="nightsFromPeriod"><?=h((string)$nuits)?></span>
</div>
</div>
</div>
<form id="acceptForm" method="post">
<input type="hidden" name="t" value="<?=h($t)?>">
<input type="hidden" name="sig" value="<?=h($sig)?>">
<div class="subcard">
<div class="title">
<h2>Tarifs</h2>
<span class="badge">modifiable</span>
</div>
<label for="price">Tarif total à renseigner</label>
<input id="price" name="tarif" class="inputBrown" type="text"
placeholder="ex : 420 € / séjour"
value="<?=h($tarif_prefill)?>" />
<div class="row2">
<div>
<label for="nightsView">Nombre de nuitées</label>
<!-- Visible mais non modifiable -->
<input id="nightsView" type="number" min="0" max="365"
value="<?=h((string)$nuits)?>"
readonly
style="background:#f3f4f6; cursor:not-allowed;" />
<!-- Valeur réellement envoyée -->
<input id="nights" name="nuits" type="hidden" value="<?=h((string)$nuits)?>" />
</div>
<div>
<label for="guests">Nombre de personnes</label>
<input id="guests" name="voyageur_nombre" type="number" min="1" max="99"
placeholder="ex : 2"
value="<?=h($guests)?>" />
</div>
</div>
<div class="row2">
<div>
<label for="dateStart">Date début</label>
<input id="dateStart" name="date_debut" type="date" value="<?=h($date_debut)?>" required>
</div>
<div>
<label for="dateEnd">Date fin</label>
<input id="dateEnd" name="date_fin" type="date" value="<?=h($date_fin)?>" required>
</div>
</div>
<label for="hostMsg">Message de l’hôte (optionnel)</label>
<textarea id="hostMsg" name="message_proprio" rows="7"
placeholder="Ex: Merci pour votre demande, voici les modalités…"><?=h($message_host_prefill)?></textarea>
<div class="mini" style="margin-top:10px">
</div>
</div>
<div class="subcard">
<div class="title">
<h2>Info <?=h($hebergement_nom)?></h2>
<span class="badge">éditable</span>
</div>
<div class="info">
<b>Les infos ci-dessous sont aussi modifiables depuis</b>
votre <a href="https://www.myreco.online/administration/gestion/index.php?token=<?=h($token)?>" target="_blank" rel="noopener noreferrer" style="color:#9e8469;text-decoration:underline;">tableau de bord MyReco</a>.
</div>
<div class="row2">
<div>
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
<label for="directUrl" style="margin:0;">Votre site de réservation directe</label>
<button type="button" class="btn-mini" data-toggle-fill="#directUrl">Vider</button>
</div>
<input id="directUrl" name="site_reservation" type="url" placeholder="https://…"
value="<?=h($site_reservation)?>" />
</div>
<div>
<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;">
<label for="directEmail" style="margin:0;">Votre email de contact</label>
<button type="button" class="btn-mini" data-toggle-fill="#directEmail">Vider</button>
</div>
<input id="directEmail" name="email_reservation" type="email" placeholder="resa@…"
value="<?=h($email_reservation)?>" />
</div>
</div>
<label for="siteUrl">Votre site web</label>
<input id="siteUrl" name="site" type="url" placeholder="https://…"
value="<?=h($site)?>" />
<div class="btnRow">
<button class="btn secondary" type="button" id="btnPreview">Aperçu du mail</button>
<button class="btn primary" type="button" id="btnSubmit" <?=($response_status!=='pending'?'disabled':'')?>>
Envoyer
</button>
<div class="btnNote"></div>
</div>
</div>
</form>
</div>
</div>
<div class="colRight">
<div class="previewWrap">
<div class="previewHeader">
<h2>Aperçu du mail</h2>
<span class="badge">aperçu</span>
</div>
<div class="preview" aria-label="aperçu email">
<div class="meta">
<span class="tag"><b>Objet :</b> <span id="previewSubject"><?=h("Disponibilité confirmée — $hebergement_nom — $date_debut_fr → $date_fin_fr")?></span></span>
<span class="tag"><b>À :</b> <span id="previewTo"><?=h($email_voyageur)?></span></span>
</div>
<div class="mini">
</div>
<iframe class="iframe" id="previewFrame"></iframe>
</div>
<div class="mini" style="margin-top:10px;">
</div>
</div>
</div>
</div>
</section>
</div>
<script>
<?php
$tarif_nuit_js = (string)($ctx['hebergement']['tarif_nuit'] ?? '');
?>
const TARIF_NUIT_RAW = <?= json_encode($tarif_nuit_js, JSON_UNESCAPED_UNICODE) ?>;
const HEB_NAME = <?= json_encode($hebergement_nom, JSON_UNESCAPED_UNICODE) ?>;
function fmtFr(ymd){
if (!ymd) return '—';
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(ymd);
if (!m) return ymd;
return m[3] + '/' + m[2] + '/' + m[1];
}
(function(){
const form = document.getElementById('acceptForm');
const frame = document.getElementById('previewFrame');
const btnPreview = document.getElementById('btnPreview');
const btnSubmit = document.getElementById('btnSubmit');
const priceInp = document.getElementById('price'); // ✅ ici
const dateStart = document.getElementById('dateStart');
const dateEnd = document.getElementById('dateEnd');
const nightsInp = document.getElementById('nights'); // hidden envoyé
const nightsView = document.getElementById('nightsView'); // visible readonly
const guestsInp = document.getElementById('guests');
const periodeText = document.getElementById('periodeText');
const nightsFromPeriod = document.getElementById('nightsFromPeriod');
const guestsText = document.getElementById('guestsText');
const previewSubject = document.getElementById('previewSubject');
const previewTo = document.getElementById('previewTo');
function parsePriceToNumber(raw){
if (!raw) return null;
let s = String(raw).trim();
if (!s) return null;
s = s.replace(/€/g, '').replace(/\s+/g, '');
s = s.replace(',', '.');
if (!/^\d+(\.\d{1,2})?$/.test(s)) return null;
const n = Number(s);
return Number.isFinite(n) ? n : null;
}
function formatPriceFr(n){
try {
return n.toLocaleString('fr-FR', {minimumFractionDigits: 2, maximumFractionDigits: 2});
} catch(e){
return String(Math.round(n * 100) / 100).replace('.', ',');
}
}
function computeTotal(tarifNuit, nights){
if (!(tarifNuit > 0) || !(nights > 0)) return null;
return tarifNuit * nights;
}
function diffNights(a, b){
if (!a || !b) return null;
const da = new Date(a + 'T00:00:00');
const db = new Date(b + 'T00:00:00');
const ms = db - da;
if (!isFinite(ms)) return null;
const d = Math.round(ms / 86400000);
return d < 0 ? 0 : d;
}
function syncComputed(){
const ds = dateStart?.value || '';
const de = dateEnd?.value || '';
if (periodeText) periodeText.textContent = (ds || '—') + ' → ' + (de || '—');
const n = diffNights(ds, de);
if (n !== null) {
if (nightsInp) nightsInp.value = String(n); // hidden
if (nightsView) nightsView.value = String(n); // visible
if (nightsFromPeriod) nightsFromPeriod.textContent = String(n);
}
if (guestsText && guestsInp) guestsText.textContent = guestsInp.value || '—';
if (previewSubject){
previewSubject.textContent = "Disponibilité confirmée — " + (HEB_NAME || '—') + " — " + fmtFr(ds) + " → " + fmtFr(de);
}
// ✅ Auto-calc tarif total (si user n’a pas modifié à la main)
if (priceInp && nightsInp && guestsInp){
// si dataset.auto n'existe pas encore, on le considère en auto
if (!priceInp.dataset.auto) priceInp.dataset.auto = '1';
const autoAllowed = (!priceInp.value || priceInp.dataset.auto !== '0');
if (autoAllowed){
const tn = parsePriceToNumber(TARIF_NUIT_RAW);
const nNights = Number(nightsInp.value || 0);
const total = computeTotal(tn, nNights);
if (total !== null){
priceInp.value = formatPriceFr(total);
priceInp.dataset.auto = '1';
}
}
}
}
async function post(action){
const fd = new FormData(form);
fd.set('action', action);
const res = await fetch(window.location.href.split('#')[0], {
method: 'POST',
body: fd,
headers: { 'Accept': 'application/json' }
});
const text = await res.text();
try {
return JSON.parse(text);
} catch(e){
console.error("Réponse non JSON:", text);
throw new Error("Réponse serveur invalide (JSON attendu). Regarde la console.");
}
}
async function loadPreview(scroll = false){
if (!btnPreview || !frame) return;
btnPreview.disabled = true;
try{
const data = await post('preview');
if (!data.ok){
alert(data.error || 'Erreur');
return;
}
frame.style.display = 'block';
const doc = frame.contentDocument || frame.contentWindow.document;
doc.open(); doc.write(data.html || ''); doc.close();
if (data.subject && previewSubject) previewSubject.textContent = data.subject;
if (data.to && previewTo) previewTo.textContent = data.to;
if (scroll){
frame.scrollIntoView({behavior:'smooth', block:'start'});
}
} catch(e){
alert(e.message || "Erreur");
} finally {
btnPreview.disabled = false;
}
}
btnPreview?.addEventListener('click', () => loadPreview(true));
// ✅ Aperçu affiché par défaut
loadPreview(false);
btnSubmit?.addEventListener('click', async () => {
if (!confirm("Confirmez vous la validation de votre réponse ?")) return;
btnSubmit.disabled = true;
try{
const data = await post('submit');
if (!data.ok){
alert(data.error || 'Erreur');
btnSubmit.disabled = false;
return;
}
location.reload();
} catch(e){
alert(e.message || "Erreur");
btnSubmit.disabled = false;
}
});
// ✅ si l'utilisateur modifie => on coupe l'auto tarif
priceInp?.addEventListener('input', () => { priceInp.dataset.auto = '0'; });
// recalcul auto quand dates / guests changent
dateStart?.addEventListener('change', syncComputed);
dateEnd?.addEventListener('change', syncComputed);
guestsInp?.addEventListener('input', syncComputed);
// init
syncComputed();
})();
// Boutons "Vider / Remplir" pour inputs
document.querySelectorAll('[data-toggle-fill]').forEach((btn) => {
const sel = btn.getAttribute('data-toggle-fill');
const input = document.querySelector(sel);
if (!input) return;
// On garde la valeur initiale (celle du HTML/PHP)
if (!input.dataset.initialValue) input.dataset.initialValue = input.value || '';
const refreshLabel = () => {
btn.textContent = (input.value && input.value.trim() !== '') ? 'Vider' : 'Remplir';
};
btn.addEventListener('click', async () => {
const current = (input.value || '').trim();
if (current !== '') {
input.value = '';
} else {
input.value = input.dataset.initialValue || '';
}
input.dispatchEvent(new Event('input', {bubbles:true}));
input.dispatchEvent(new Event('change', {bubbles:true}));
refreshLabel();
// ✅ déclenche l'aperçu après changement
if (typeof loadPreview === 'function') {
await loadPreview(false); // false = pas de scroll
} else {
// fallback si tu n'as pas loadPreview
document.querySelector('#btnPreview')?.click();
}
});
input.addEventListener('input', refreshLabel);
refreshLabel();
});
</script>
</body>
</html>