Deploy to Pacha Family / deploy (push) Failing after 3s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
298 lines
15 KiB
PHP
298 lines
15 KiB
PHP
<?php
|
|
require __DIR__ . '/includes/auth.php';
|
|
require_login();
|
|
require_once __DIR__ . '/includes/meta_db.php';
|
|
require_once __DIR__ . '/includes/i18n.php';
|
|
|
|
$user_id = $_SESSION['user']['id'];
|
|
$family_id = $_SESSION['user']['family_id'];
|
|
|
|
$error = null;
|
|
$success = null;
|
|
|
|
// ─── Actions ──────────────────────────────────────────────────────────────────
|
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$action = $_POST['action'] ?? '';
|
|
|
|
if ($action === 'set_modules' && $family_id) {
|
|
$all = ['calendar', 'budget', 'holidays', 'gifts'];
|
|
$enabled = array_values(array_filter($all, fn($m) => isset($_POST['mod_' . $m])));
|
|
if (empty($enabled)) {
|
|
$error = "Vous devez garder au moins un module actif.";
|
|
} else {
|
|
$meta_pdo->prepare("UPDATE families SET enabled_modules = ? WHERE id = ?")
|
|
->execute([json_encode($enabled), $family_id]);
|
|
$_SESSION['enabled_modules'] = $enabled;
|
|
$success = "Modules mis à jour.";
|
|
}
|
|
}
|
|
|
|
if ($action === 'set_lang') {
|
|
$lang = $_POST['lang'] ?? 'fr';
|
|
if (in_array($lang, ['fr', 'ca', 'en'])) {
|
|
$_SESSION['app_lang'] = $lang;
|
|
$meta_pdo->prepare("UPDATE users SET lang = ? WHERE id = ?")
|
|
->execute([$lang, $user_id]);
|
|
$success = "Language updated.";
|
|
}
|
|
}
|
|
|
|
if ($action === 'update_profile') {
|
|
$display_name = trim($_POST['display_name'] ?? '');
|
|
if (!$display_name) {
|
|
$error = "Le prénom ne peut pas être vide.";
|
|
} else {
|
|
$meta_pdo->prepare("UPDATE users SET display_name = ? WHERE id = ?")
|
|
->execute([$display_name, $user_id]);
|
|
$_SESSION['user']['display_name'] = $display_name;
|
|
$success = "Profil mis à jour.";
|
|
}
|
|
}
|
|
|
|
if ($action === 'change_password') {
|
|
$current = $_POST['current_password'] ?? '';
|
|
$new = $_POST['new_password'] ?? '';
|
|
$confirm = $_POST['confirm_password'] ?? '';
|
|
|
|
$row = $meta_pdo->prepare("SELECT password_hash FROM users WHERE id = ?");
|
|
$row->execute([$user_id]);
|
|
$row = $row->fetch();
|
|
|
|
if (!password_verify($current, $row['password_hash'])) {
|
|
$error = "Mot de passe actuel incorrect.";
|
|
} elseif (strlen($new) < 6) {
|
|
$error = "Le nouveau mot de passe doit faire au moins 6 caractères.";
|
|
} elseif ($new !== $confirm) {
|
|
$error = "Les mots de passe ne correspondent pas.";
|
|
} else {
|
|
$meta_pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?")
|
|
->execute([password_hash($new, PASSWORD_BCRYPT), $user_id]);
|
|
$success = "Mot de passe modifié avec succès.";
|
|
}
|
|
}
|
|
|
|
if ($action === 'update_family_name' && $family_id) {
|
|
$name = trim($_POST['family_name'] ?? '');
|
|
if ($name) {
|
|
$meta_pdo->prepare("UPDATE families SET name = ? WHERE id = ?")
|
|
->execute([$name, $family_id]);
|
|
$success = "Nom de l'espace mis à jour.";
|
|
}
|
|
}
|
|
|
|
if ($action === 'regen_invite' && $family_id) {
|
|
$new_code = bin2hex(random_bytes(8));
|
|
$meta_pdo->prepare("UPDATE families SET invite_code = ? WHERE id = ?")
|
|
->execute([$new_code, $family_id]);
|
|
$success = "Nouveau code d'invitation généré.";
|
|
}
|
|
}
|
|
|
|
// ─── Chargement données ───────────────────────────────────────────────────────
|
|
$user = $meta_pdo->prepare("SELECT * FROM users WHERE id = ?");
|
|
$user->execute([$user_id]);
|
|
$user = $user->fetch();
|
|
|
|
$family = null;
|
|
$members = [];
|
|
if ($family_id) {
|
|
$fam = $meta_pdo->prepare("SELECT * FROM families WHERE id = ?");
|
|
$fam->execute([$family_id]);
|
|
$family = $fam->fetch();
|
|
|
|
$mem = $meta_pdo->prepare("SELECT display_name, username, created_at, is_admin FROM users WHERE family_id = ? ORDER BY id");
|
|
$mem->execute([$family_id]);
|
|
$members = $mem->fetchAll();
|
|
}
|
|
|
|
$pageTitle = "Paramètres — HouseHub";
|
|
$activePage = "settings";
|
|
require __DIR__ . '/header.php';
|
|
?>
|
|
|
|
<div class="pf-container" style="max-width:680px;margin:32px auto;padding:0 16px">
|
|
|
|
<h1 style="font-size:1.4rem;font-weight:700;margin-bottom:32px">⚙️ Paramètres</h1>
|
|
|
|
<?php if ($success): ?>
|
|
<div style="background:#dcfce7;border:1px solid #86efac;border-radius:8px;padding:12px 16px;margin-bottom:24px;color:#166534">✓ <?= htmlspecialchars($success) ?></div>
|
|
<?php endif; ?>
|
|
<?php if ($error): ?>
|
|
<div style="background:#fee2e2;border:1px solid #fca5a5;border-radius:8px;padding:12px 16px;margin-bottom:24px;color:#991b1b">✗ <?= htmlspecialchars($error) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<!-- ── Langue ─────────────────────────────────────────────────────────── -->
|
|
<section style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:24px">
|
|
<h2 style="font-size:1rem;font-weight:700;margin-bottom:20px">🌐 Langue / Language</h2>
|
|
<form method="post" style="display:flex;gap:10px;flex-wrap:wrap">
|
|
<input type="hidden" name="action" value="set_lang">
|
|
<?php
|
|
$s = 'style="vertical-align:middle;border-radius:2px;margin-right:5px"';
|
|
$fr_flag = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" width="20" height="13" '.$s.'><rect width="6" height="12" fill="#002395"/><rect x="6" width="6" height="12" fill="#fff"/><rect x="12" width="6" height="12" fill="#ED2939"/></svg>';
|
|
$en_flag = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 30" width="20" height="13" '.$s.'><rect width="60" height="30" fill="#012169"/><path d="M0,0 L60,30 M60,0 L0,30" stroke="#fff" stroke-width="6"/><path d="M0,0 L60,30 M60,0 L0,30" stroke="#C8102E" stroke-width="4"/><path d="M30,0 V30 M0,15 H60" stroke="#fff" stroke-width="10"/><path d="M30,0 V30 M0,15 H60" stroke="#C8102E" stroke-width="6"/></svg>';
|
|
$senyera = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 12" width="20" height="13" '.$s.'><rect width="18" height="12" fill="#FCDD09"/><rect y="1.33" width="18" height="1.33" fill="#DA121A"/><rect y="4" width="18" height="1.33" fill="#DA121A"/><rect y="6.67" width="18" height="1.33" fill="#DA121A"/><rect y="9.33" width="18" height="1.33" fill="#DA121A"/></svg>';
|
|
$langs = [
|
|
'fr' => ['flag' => $fr_flag, 'label' => 'Français'],
|
|
'en' => ['flag' => $en_flag, 'label' => 'English'],
|
|
'ca' => ['flag' => $senyera, 'label' => 'Català'],
|
|
];
|
|
$currentLang = $_SESSION['app_lang'] ?? 'fr';
|
|
foreach ($langs as $code => $lang):
|
|
?>
|
|
<button type="submit" name="lang" value="<?= $code ?>"
|
|
class="pf-btn <?= $currentLang === $code ? '' : 'btn-secondary' ?>"
|
|
style="<?= $currentLang === $code ? 'pointer-events:none' : '' ?>">
|
|
<?= $lang['flag'] ?> <?= $lang['label'] ?>
|
|
</button>
|
|
<?php endforeach; ?>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- ── Modules ────────────────────────────────────────────────────────── -->
|
|
<?php if ($family_id): ?>
|
|
<section style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:24px">
|
|
<h2 style="font-size:1rem;font-weight:700;margin-bottom:6px">🧩 Modules actifs</h2>
|
|
<p style="color:#64748b;font-size:0.85rem;margin-bottom:20px">Choisissez les modules visibles dans la navigation (partagé avec tous les membres de l'espace).</p>
|
|
<?php
|
|
$enabledMods = $_SESSION['enabled_modules'] ?? ['calendar','budget','holidays','gifts'];
|
|
$allModules = [
|
|
'calendar' => ['icon' => '📅', 'label' => tr('menu_calendar')],
|
|
'budget' => ['icon' => '💰', 'label' => tr('menu_budget')],
|
|
'holidays' => ['icon' => '🏖️', 'label' => tr('menu_holidays')],
|
|
'gifts' => ['icon' => '🎁', 'label' => tr('menu_gifts')],
|
|
];
|
|
?>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="set_modules">
|
|
<div style="display:flex;flex-direction:column;gap:12px;margin-bottom:20px">
|
|
<?php foreach ($allModules as $key => $mod): $active = in_array($key, $enabledMods); ?>
|
|
<label style="display:flex;align-items:center;gap:14px;padding:14px 16px;border:2px solid <?= $active ? '#2563eb' : '#e2e8f0' ?>;border-radius:10px;cursor:pointer;background:<?= $active ? '#eff6ff' : '#f8fafc' ?>;transition:all .15s">
|
|
<input type="checkbox" name="mod_<?= $key ?>" <?= $active ? 'checked' : '' ?>
|
|
style="width:18px;height:18px;accent-color:#2563eb;cursor:pointer"
|
|
onchange="this.closest('label').style.borderColor=this.checked?'#2563eb':'#e2e8f0';this.closest('label').style.background=this.checked?'#eff6ff':'#f8fafc'">
|
|
<span style="font-size:1.3rem"><?= $mod['icon'] ?></span>
|
|
<span style="font-weight:600;font-size:0.95rem"><?= $mod['label'] ?></span>
|
|
</label>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<button type="submit" class="pf-btn">Enregistrer</button>
|
|
</form>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<!-- ── Profil ──────────────────────────────────────────────────────────── -->
|
|
<section style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:24px">
|
|
<h2 style="font-size:1rem;font-weight:700;margin-bottom:20px">👤 Mon profil</h2>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="update_profile">
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Nom d'utilisateur</label>
|
|
<input type="text" class="pf-input" value="<?= htmlspecialchars($user['username']) ?>" disabled style="background:#f8fafc;color:#94a3b8">
|
|
</div>
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Prénom / Pseudo affiché</label>
|
|
<input type="text" name="display_name" class="pf-input" value="<?= htmlspecialchars($user['display_name']) ?>" required>
|
|
</div>
|
|
<button type="submit" class="pf-btn">Enregistrer</button>
|
|
</form>
|
|
</section>
|
|
|
|
<!-- ── Mot de passe ────────────────────────────────────────────────────── -->
|
|
<section style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:24px">
|
|
<h2 style="font-size:1rem;font-weight:700;margin-bottom:20px">🔒 Changer le mot de passe</h2>
|
|
<form method="post">
|
|
<input type="hidden" name="action" value="change_password">
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Mot de passe actuel</label>
|
|
<input type="password" name="current_password" class="pf-input" required placeholder="••••••">
|
|
</div>
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Nouveau mot de passe</label>
|
|
<input type="password" name="new_password" class="pf-input" required placeholder="••••••" autocomplete="new-password">
|
|
</div>
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Confirmer</label>
|
|
<input type="password" name="confirm_password" class="pf-input" required placeholder="••••••" autocomplete="new-password">
|
|
</div>
|
|
<button type="submit" class="pf-btn">Modifier</button>
|
|
</form>
|
|
</section>
|
|
|
|
<?php if ($family): ?>
|
|
<!-- ── Espace familial ─────────────────────────────────────────────────── -->
|
|
<section style="background:white;border:1px solid #e2e8f0;border-radius:12px;padding:24px;margin-bottom:24px">
|
|
<h2 style="font-size:1rem;font-weight:700;margin-bottom:20px">🏠 Mon espace familial</h2>
|
|
|
|
<form method="post" style="margin-bottom:20px">
|
|
<input type="hidden" name="action" value="update_family_name">
|
|
<div class="pf-form-group">
|
|
<label class="pf-label">Nom de l'espace</label>
|
|
<div style="display:flex;gap:8px">
|
|
<input type="text" name="family_name" class="pf-input" value="<?= htmlspecialchars($family['name']) ?>" required>
|
|
<button type="submit" class="pf-btn" style="flex-shrink:0">Enregistrer</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Code d'invitation -->
|
|
<div style="background:#f8fafc;border:2px dashed #cbd5e1;border-radius:10px;padding:20px">
|
|
<p style="font-size:0.85rem;color:#64748b;margin-bottom:8px">Code d'invitation — partagez-le pour inviter quelqu'un</p>
|
|
<div style="display:flex;align-items:center;gap:12px;flex-wrap:wrap">
|
|
<code id="invite-code" style="font-size:1.3rem;font-weight:700;letter-spacing:3px;color:#1e40af;cursor:pointer"
|
|
onclick="copyCode()" title="Cliquer pour copier">
|
|
<?= htmlspecialchars($family['invite_code']) ?>
|
|
</code>
|
|
<span id="copy-msg" style="color:#16a34a;font-size:0.85rem;opacity:0;transition:opacity .3s">✓ Copié !</span>
|
|
<form method="post" style="margin-left:auto">
|
|
<input type="hidden" name="action" value="regen_invite">
|
|
<button type="submit" class="pf-btn btn-secondary" style="font-size:0.85rem"
|
|
onclick="return confirm('Regénérer le code ? L\'ancien sera invalidé.')">
|
|
🔄 Nouveau code
|
|
</button>
|
|
</form>
|
|
</div>
|
|
<p style="margin-top:12px;font-size:0.8rem;color:#94a3b8">
|
|
Lien d'inscription : <strong><?= $_SERVER['HTTP_HOST'] ?>/register.php</strong>
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Membres -->
|
|
<h3 style="font-size:0.95rem;font-weight:600;margin:20px 0 12px">Membres (<?= count($members) ?>)</h3>
|
|
<div style="display:flex;flex-direction:column;gap:8px">
|
|
<?php foreach ($members as $m): ?>
|
|
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 14px;background:#f8fafc;border-radius:8px">
|
|
<div>
|
|
<strong><?= htmlspecialchars($m['display_name']) ?></strong>
|
|
<span style="color:#94a3b8;font-size:0.85rem;margin-left:6px">@<?= htmlspecialchars($m['username']) ?></span>
|
|
<?php if ($m['is_admin']): ?>
|
|
<span style="background:#dbeafe;color:#1d4ed8;padding:1px 7px;border-radius:8px;font-size:0.75rem;font-weight:600;margin-left:6px">Admin</span>
|
|
<?php endif; ?>
|
|
</div>
|
|
<span style="font-size:0.8rem;color:#94a3b8"><?= date('d/m/Y', strtotime($m['created_at'])) ?></span>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</section>
|
|
<?php endif; ?>
|
|
|
|
<?php if ($_SESSION['user']['is_admin']): ?>
|
|
<div style="text-align:center;margin-top:8px">
|
|
<a href="/admin/" style="color:#2563eb;font-size:0.9rem">→ Panneau d'administration</a>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
</div>
|
|
|
|
<script>
|
|
function copyCode() {
|
|
const code = document.getElementById('invite-code').textContent.trim();
|
|
navigator.clipboard.writeText(code).then(() => {
|
|
const msg = document.getElementById('copy-msg');
|
|
msg.style.opacity = '1';
|
|
setTimeout(() => msg.style.opacity = '0', 2000);
|
|
});
|
|
}
|
|
</script>
|
|
|
|
<?php require __DIR__ . '/footer.php'; ?>
|