feat: Add 100% completion modal settings
Co-authored-by: mathieumoran1 <[email protected]>
This commit is contained in:
co-authored by
mathieumoran1
parent
1389b004f6
commit
4aa969414d
@@ -65,6 +65,11 @@
|
|||||||
Réglages
|
Réglages
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item" role="presentation">
|
||||||
|
<button class="nav-link" id="admin-modal-100-tab" data-bs-toggle="tab" data-bs-target="#admin-modal-100" type="button" role="tab" aria-controls="admin-modal-100" aria-selected="false">
|
||||||
|
Modal 100%
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
<li class="nav-item" role="presentation">
|
<li class="nav-item" role="presentation">
|
||||||
<button class="nav-link" id="admin-users-tab" data-bs-toggle="tab" data-bs-target="#admin-users" type="button" role="tab" aria-controls="admin-users" aria-selected="false">
|
<button class="nav-link" id="admin-users-tab" data-bs-toggle="tab" data-bs-target="#admin-users" type="button" role="tab" aria-controls="admin-users" aria-selected="false">
|
||||||
Utilisateurs
|
Utilisateurs
|
||||||
@@ -99,6 +104,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="tab-pane fade" id="admin-modal-100" role="tabpanel" aria-labelledby="admin-modal-100-tab">
|
||||||
|
<div class="row g-3">
|
||||||
|
<div class="col-12 col-xxl-6">
|
||||||
|
<div class="card p-3">
|
||||||
|
<h4 class="mb-1">Contenu “Modal 100%”</h4>
|
||||||
|
<p class="text-muted mb-3">
|
||||||
|
Contenu affiché quand un utilisateur a vu 100% des films dans la checklist.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="completion_modal_title" class="form-label">Titre</label>
|
||||||
|
<input type="text" class="form-control" id="completion_modal_title" maxlength="200" placeholder="Félicitations! ...">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="completion_modal_body_text" class="form-label">Texte (optionnel)</label>
|
||||||
|
<textarea class="form-control" id="completion_modal_body_text" rows="4" placeholder="Texte affiché dans le modal (retours à la ligne conservés)"></textarea>
|
||||||
|
<div class="form-text">Affiché en texte brut (safe). Laisse vide si tu n’en veux pas.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="completion_modal_body_html" class="form-label">HTML (optionnel)</label>
|
||||||
|
<textarea class="form-control font-monospace" id="completion_modal_body_html" rows="6" placeholder="<p>…</p>"></textarea>
|
||||||
|
<div class="form-text">
|
||||||
|
Affiché comme HTML (le serveur retire les <code><script></code> et les handlers <code>on*</code>).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="completion_modal_video_src" class="form-label">Video src (optionnel)</label>
|
||||||
|
<input type="text" class="form-control" id="completion_modal_video_src" maxlength="2048" placeholder="video/reward.mp4">
|
||||||
|
<div class="form-text">Chemin relatif (ex: <code>video/reward.mp4</code>) ou URL complète. Vide = pas de vidéo.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex flex-wrap justify-content-end gap-2">
|
||||||
|
<button type="button" id="completion_modal_reload" class="btn btn-outline-secondary">Recharger</button>
|
||||||
|
<button type="button" id="completion_modal_save" class="btn btn-warning">Enregistrer</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="col-12 col-xxl-6">
|
||||||
|
<div class="card p-3">
|
||||||
|
<h4 class="mb-1">Aperçu</h4>
|
||||||
|
<div class="text-muted mb-3">Aperçu live (non sauvegardé tant que tu n’enregistres pas).</div>
|
||||||
|
|
||||||
|
<div class="border rounded p-3 bg-white">
|
||||||
|
<div class="d-flex align-items-center justify-content-between gap-2 mb-2">
|
||||||
|
<div class="fw-semibold" id="completion_modal_preview_title">—</div>
|
||||||
|
<span class="badge bg-secondary">Preview</span>
|
||||||
|
</div>
|
||||||
|
<div id="completion_modal_preview_custom" class="text-start d-none"></div>
|
||||||
|
<div id="completion_modal_preview_text" class="text-start" style="white-space: pre-wrap;"></div>
|
||||||
|
|
||||||
|
<div class="mt-3">
|
||||||
|
<video id="completion_modal_preview_video" width="75%" controls class="d-none">
|
||||||
|
<source id="completion_modal_preview_video_source" src="" type="video/mp4">
|
||||||
|
Votre navigateur ne supporte pas la vidéo.
|
||||||
|
</video>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="tab-pane fade" id="admin-users" role="tabpanel" aria-labelledby="admin-users-tab">
|
<div class="tab-pane fade" id="admin-users" role="tabpanel" aria-labelledby="admin-users-tab">
|
||||||
<div class="row g-3">
|
<div class="row g-3">
|
||||||
<div class="col-12 col-xl-8">
|
<div class="col-12 col-xl-8">
|
||||||
|
|||||||
@@ -1,6 +1,81 @@
|
|||||||
window.onload = async function () {
|
window.onload = async function () {
|
||||||
const token = localStorage.getItem('auth_token');
|
const token = localStorage.getItem('auth_token');
|
||||||
|
|
||||||
|
const DEFAULT_COMPLETION_MODAL = Object.freeze({
|
||||||
|
title: 'Félicitations! very nice 🎉🎉🎉',
|
||||||
|
bodyText: '',
|
||||||
|
videoSrc: 'video/reward.mp4',
|
||||||
|
bodyHtml: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
function sanitizeHtml(raw) {
|
||||||
|
const html = String(raw || '');
|
||||||
|
const withoutScripts = html.replace(/<script\b[\s\S]*?<\/script>/gi, '');
|
||||||
|
const withoutOnAttrs = withoutScripts
|
||||||
|
.replace(/\son\w+="[^"]*"/gi, '')
|
||||||
|
.replace(/\son\w+='[^']*'/gi, '')
|
||||||
|
.replace(/\son\w+=\S+/gi, '');
|
||||||
|
return withoutOnAttrs.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCompletionModalContent() {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/settings/completion-modal', { method: 'GET' });
|
||||||
|
if (!res.ok) throw new Error('Failed to fetch completion modal');
|
||||||
|
const data = await res.json();
|
||||||
|
return {
|
||||||
|
title: typeof data?.title === 'string' ? data.title : DEFAULT_COMPLETION_MODAL.title,
|
||||||
|
bodyText: typeof data?.bodyText === 'string' ? data.bodyText : DEFAULT_COMPLETION_MODAL.bodyText,
|
||||||
|
videoSrc: typeof data?.videoSrc === 'string' ? data.videoSrc : DEFAULT_COMPLETION_MODAL.videoSrc,
|
||||||
|
bodyHtml: typeof data?.bodyHtml === 'string' ? data.bodyHtml : DEFAULT_COMPLETION_MODAL.bodyHtml,
|
||||||
|
};
|
||||||
|
} catch (_) {
|
||||||
|
return DEFAULT_COMPLETION_MODAL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCompletionModalContent(content) {
|
||||||
|
const titleEl = document.getElementById('videoModalLabel');
|
||||||
|
const bodyTextEl = document.getElementById('completion-modal-text');
|
||||||
|
const bodyCustomEl = document.getElementById('completion-modal-custom');
|
||||||
|
const videoEl = document.getElementById('rewardVideo');
|
||||||
|
const videoSourceEl = document.getElementById('rewardVideoSource');
|
||||||
|
|
||||||
|
const title = String(content?.title || '').trim() || DEFAULT_COMPLETION_MODAL.title;
|
||||||
|
const bodyText = String(content?.bodyText || '');
|
||||||
|
const bodyHtml = sanitizeHtml(String(content?.bodyHtml || ''));
|
||||||
|
const videoSrc = String(content?.videoSrc || '').trim();
|
||||||
|
|
||||||
|
if (titleEl) titleEl.textContent = title;
|
||||||
|
|
||||||
|
if (bodyCustomEl) {
|
||||||
|
if (bodyHtml) {
|
||||||
|
bodyCustomEl.innerHTML = bodyHtml;
|
||||||
|
bodyCustomEl.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
bodyCustomEl.innerHTML = '';
|
||||||
|
bodyCustomEl.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bodyTextEl) {
|
||||||
|
bodyTextEl.textContent = bodyText || '';
|
||||||
|
bodyTextEl.classList.toggle('d-none', !bodyText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (videoEl && videoSourceEl) {
|
||||||
|
if (videoSrc) {
|
||||||
|
videoSourceEl.setAttribute('src', videoSrc);
|
||||||
|
// reload source (so updated src is used)
|
||||||
|
try { videoEl.load(); } catch (_) {}
|
||||||
|
videoEl.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
try { videoEl.pause(); } catch (_) {}
|
||||||
|
videoEl.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function fetchActiveYear() {
|
async function fetchActiveYear() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/settings/year', { method: 'GET' });
|
const res = await fetch('/api/settings/year', { method: 'GET' });
|
||||||
@@ -18,6 +93,10 @@ window.onload = async function () {
|
|||||||
const oscarYearEl = document.getElementById('oscar-year');
|
const oscarYearEl = document.getElementById('oscar-year');
|
||||||
if (oscarYearEl) oscarYearEl.textContent = String(activeYear);
|
if (oscarYearEl) oscarYearEl.textContent = String(activeYear);
|
||||||
|
|
||||||
|
// Load (public) "100% completion" modal content at runtime.
|
||||||
|
const completionModalContent = await fetchCompletionModalContent();
|
||||||
|
applyCompletionModalContent(completionModalContent);
|
||||||
|
|
||||||
const targetDate = new Date(`March 15, ${activeYear}`);
|
const targetDate = new Date(`March 15, ${activeYear}`);
|
||||||
const currentDate = new Date();
|
const currentDate = new Date();
|
||||||
const timeDifference = targetDate - currentDate;
|
const timeDifference = targetDate - currentDate;
|
||||||
@@ -128,7 +207,11 @@ window.onload = async function () {
|
|||||||
if (percentage === 100) {
|
if (percentage === 100) {
|
||||||
videoModal.show();
|
videoModal.show();
|
||||||
launchConfetti();
|
launchConfetti();
|
||||||
document.getElementById("rewardVideo").play();
|
const rewardVideo = document.getElementById("rewardVideo");
|
||||||
|
// Only attempt autoplay if we actually have a visible <video>.
|
||||||
|
if (rewardVideo && !rewardVideo.classList.contains('d-none')) {
|
||||||
|
rewardVideo.play().catch(() => {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,6 +304,7 @@ window.onload = async function () {
|
|||||||
const videoModal = document.getElementById("videoModal");
|
const videoModal = document.getElementById("videoModal");
|
||||||
const rewardVideo = document.getElementById("rewardVideo");
|
const rewardVideo = document.getElementById("rewardVideo");
|
||||||
videoModal.addEventListener("hidden.bs.modal", function () {
|
videoModal.addEventListener("hidden.bs.modal", function () {
|
||||||
|
if (!rewardVideo) return;
|
||||||
rewardVideo.pause();
|
rewardVideo.pause();
|
||||||
rewardVideo.currentTime = 0;
|
rewardVideo.currentTime = 0;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -41,6 +41,20 @@ window.onload = async function () {
|
|||||||
const activeYearInput = document.getElementById('active_oscar_year');
|
const activeYearInput = document.getElementById('active_oscar_year');
|
||||||
const saveActiveYearBtn = document.getElementById('save-active-year');
|
const saveActiveYearBtn = document.getElementById('save-active-year');
|
||||||
|
|
||||||
|
// Modal 100% elements
|
||||||
|
const completionModalTitleEl = document.getElementById('completion_modal_title');
|
||||||
|
const completionModalBodyTextEl = document.getElementById('completion_modal_body_text');
|
||||||
|
const completionModalBodyHtmlEl = document.getElementById('completion_modal_body_html');
|
||||||
|
const completionModalVideoSrcEl = document.getElementById('completion_modal_video_src');
|
||||||
|
const completionModalReloadBtn = document.getElementById('completion_modal_reload');
|
||||||
|
const completionModalSaveBtn = document.getElementById('completion_modal_save');
|
||||||
|
|
||||||
|
const completionModalPreviewTitleEl = document.getElementById('completion_modal_preview_title');
|
||||||
|
const completionModalPreviewTextEl = document.getElementById('completion_modal_preview_text');
|
||||||
|
const completionModalPreviewCustomEl = document.getElementById('completion_modal_preview_custom');
|
||||||
|
const completionModalPreviewVideoEl = document.getElementById('completion_modal_preview_video');
|
||||||
|
const completionModalPreviewVideoSourceEl = document.getElementById('completion_modal_preview_video_source');
|
||||||
|
|
||||||
const manageYearSelect = document.getElementById('manage_year');
|
const manageYearSelect = document.getElementById('manage_year');
|
||||||
const adminMoviesBody = document.getElementById('admin-movies-body');
|
const adminMoviesBody = document.getElementById('admin-movies-body');
|
||||||
const refreshMoviesBtn = document.getElementById('refresh-movies');
|
const refreshMoviesBtn = document.getElementById('refresh-movies');
|
||||||
@@ -67,12 +81,134 @@ window.onload = async function () {
|
|||||||
let moviesById = new Map();
|
let moviesById = new Map();
|
||||||
let activeYear = null;
|
let activeYear = null;
|
||||||
|
|
||||||
|
const DEFAULT_COMPLETION_MODAL = Object.freeze({
|
||||||
|
title: 'Félicitations! very nice 🎉🎉🎉',
|
||||||
|
bodyText: '',
|
||||||
|
videoSrc: 'video/reward.mp4',
|
||||||
|
bodyHtml: '',
|
||||||
|
});
|
||||||
|
|
||||||
function showResponse(kind, message) {
|
function showResponse(kind, message) {
|
||||||
responseEl.classList.remove('d-none', 'alert-success', 'alert-danger', 'alert-warning');
|
responseEl.classList.remove('d-none', 'alert-success', 'alert-danger', 'alert-warning');
|
||||||
responseEl.classList.add(kind === 'success' ? 'alert-success' : kind === 'warning' ? 'alert-warning' : 'alert-danger');
|
responseEl.classList.add(kind === 'success' ? 'alert-success' : kind === 'warning' ? 'alert-warning' : 'alert-danger');
|
||||||
responseEl.textContent = message;
|
responseEl.textContent = message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function sanitizeHtml(raw) {
|
||||||
|
const html = String(raw || '');
|
||||||
|
const withoutScripts = html.replace(/<script\b[\s\S]*?<\/script>/gi, '');
|
||||||
|
const withoutOnAttrs = withoutScripts
|
||||||
|
.replace(/\son\w+="[^"]*"/gi, '')
|
||||||
|
.replace(/\son\w+='[^']*'/gi, '')
|
||||||
|
.replace(/\son\w+=\S+/gi, '');
|
||||||
|
return withoutOnAttrs.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeCompletionModal(value) {
|
||||||
|
const v = value && typeof value === 'object' && !Array.isArray(value) ? value : {};
|
||||||
|
const title = typeof v.title === 'string' ? v.title : DEFAULT_COMPLETION_MODAL.title;
|
||||||
|
const bodyText = typeof v.bodyText === 'string' ? v.bodyText : DEFAULT_COMPLETION_MODAL.bodyText;
|
||||||
|
const videoSrc = typeof v.videoSrc === 'string' ? v.videoSrc : DEFAULT_COMPLETION_MODAL.videoSrc;
|
||||||
|
const bodyHtml = typeof v.bodyHtml === 'string' ? v.bodyHtml : DEFAULT_COMPLETION_MODAL.bodyHtml;
|
||||||
|
return {
|
||||||
|
title: String(title || '').trim().slice(0, 200) || DEFAULT_COMPLETION_MODAL.title,
|
||||||
|
bodyText: String(bodyText || '').slice(0, 8000),
|
||||||
|
videoSrc: String(videoSrc || '').trim().slice(0, 2048),
|
||||||
|
bodyHtml: String(bodyHtml || '').slice(0, 20000),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchCompletionModal() {
|
||||||
|
const res = await fetch('/api/settings/completion-modal', { method: 'GET' });
|
||||||
|
if (!res.ok) throw new Error(`Erreur (${res.status})`);
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
return normalizeCompletionModal(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveCompletionModal(payload) {
|
||||||
|
const res = await fetch('/api/settings/completion-modal', {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
if (!res.ok) {
|
||||||
|
const msg = data.message || data.error || `Erreur (${res.status})`;
|
||||||
|
throw new Error(msg);
|
||||||
|
}
|
||||||
|
return normalizeCompletionModal(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCompletionModalForm(value) {
|
||||||
|
if (!completionModalTitleEl || !completionModalBodyTextEl || !completionModalBodyHtmlEl || !completionModalVideoSrcEl) return;
|
||||||
|
completionModalTitleEl.value = value.title || '';
|
||||||
|
completionModalBodyTextEl.value = value.bodyText || '';
|
||||||
|
completionModalBodyHtmlEl.value = value.bodyHtml || '';
|
||||||
|
completionModalVideoSrcEl.value = value.videoSrc || '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCompletionModalFormValue() {
|
||||||
|
return normalizeCompletionModal({
|
||||||
|
title: completionModalTitleEl ? completionModalTitleEl.value : '',
|
||||||
|
bodyText: completionModalBodyTextEl ? completionModalBodyTextEl.value : '',
|
||||||
|
bodyHtml: completionModalBodyHtmlEl ? completionModalBodyHtmlEl.value : '',
|
||||||
|
videoSrc: completionModalVideoSrcEl ? completionModalVideoSrcEl.value : '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateCompletionModalPreview(value) {
|
||||||
|
if (completionModalPreviewTitleEl) {
|
||||||
|
completionModalPreviewTitleEl.textContent = value.title || DEFAULT_COMPLETION_MODAL.title;
|
||||||
|
}
|
||||||
|
|
||||||
|
const text = String(value.bodyText || '');
|
||||||
|
if (completionModalPreviewTextEl) {
|
||||||
|
completionModalPreviewTextEl.textContent = text;
|
||||||
|
completionModalPreviewTextEl.classList.toggle('d-none', !text);
|
||||||
|
}
|
||||||
|
|
||||||
|
const safeHtml = sanitizeHtml(value.bodyHtml || '');
|
||||||
|
if (completionModalPreviewCustomEl) {
|
||||||
|
if (safeHtml) {
|
||||||
|
completionModalPreviewCustomEl.innerHTML = safeHtml;
|
||||||
|
completionModalPreviewCustomEl.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
completionModalPreviewCustomEl.innerHTML = '';
|
||||||
|
completionModalPreviewCustomEl.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const videoSrc = String(value.videoSrc || '').trim();
|
||||||
|
if (completionModalPreviewVideoEl && completionModalPreviewVideoSourceEl) {
|
||||||
|
if (videoSrc) {
|
||||||
|
completionModalPreviewVideoSourceEl.setAttribute('src', videoSrc);
|
||||||
|
try { completionModalPreviewVideoEl.load(); } catch (_) {}
|
||||||
|
completionModalPreviewVideoEl.classList.remove('d-none');
|
||||||
|
} else {
|
||||||
|
try { completionModalPreviewVideoEl.pause(); } catch (_) {}
|
||||||
|
completionModalPreviewVideoEl.classList.add('d-none');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadCompletionModalIntoUi() {
|
||||||
|
// If the tab isn't in DOM (older HTML), just skip gracefully.
|
||||||
|
if (!completionModalTitleEl) return;
|
||||||
|
try {
|
||||||
|
const value = await fetchCompletionModal();
|
||||||
|
setCompletionModalForm(value);
|
||||||
|
updateCompletionModalPreview(getCompletionModalFormValue());
|
||||||
|
} catch (err) {
|
||||||
|
// Still show defaults so admin can edit even if network hiccups.
|
||||||
|
setCompletionModalForm(DEFAULT_COMPLETION_MODAL);
|
||||||
|
updateCompletionModalPreview(getCompletionModalFormValue());
|
||||||
|
showResponse('warning', err.message || 'Impossible de charger le contenu du modal.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const userResetResponseEl = document.getElementById('user-reset-response');
|
const userResetResponseEl = document.getElementById('user-reset-response');
|
||||||
const userResetForm = document.getElementById('reset-user-password-form');
|
const userResetForm = document.getElementById('reset-user-password-form');
|
||||||
const userResetEmailEl = document.getElementById('reset_user_email');
|
const userResetEmailEl = document.getElementById('reset_user_email');
|
||||||
@@ -139,6 +275,55 @@ window.onload = async function () {
|
|||||||
window.location.href = '/';
|
window.location.href = '/';
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Modal 100% live preview wiring
|
||||||
|
function hookModal100Inputs() {
|
||||||
|
if (!completionModalTitleEl) return;
|
||||||
|
const inputs = [completionModalTitleEl, completionModalBodyTextEl, completionModalBodyHtmlEl, completionModalVideoSrcEl].filter(Boolean);
|
||||||
|
inputs.forEach((el) => {
|
||||||
|
el.addEventListener('input', () => {
|
||||||
|
updateCompletionModalPreview(getCompletionModalFormValue());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (completionModalReloadBtn) {
|
||||||
|
completionModalReloadBtn.addEventListener('click', async () => {
|
||||||
|
completionModalReloadBtn.disabled = true;
|
||||||
|
const oldText = completionModalReloadBtn.textContent;
|
||||||
|
completionModalReloadBtn.textContent = 'Chargement...';
|
||||||
|
try {
|
||||||
|
await loadCompletionModalIntoUi();
|
||||||
|
showResponse('success', 'Contenu rechargé.');
|
||||||
|
} catch (_) {
|
||||||
|
// loadCompletionModalIntoUi already handles messaging
|
||||||
|
} finally {
|
||||||
|
completionModalReloadBtn.disabled = false;
|
||||||
|
completionModalReloadBtn.textContent = oldText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completionModalSaveBtn) {
|
||||||
|
completionModalSaveBtn.addEventListener('click', async () => {
|
||||||
|
completionModalSaveBtn.disabled = true;
|
||||||
|
const oldText = completionModalSaveBtn.textContent;
|
||||||
|
completionModalSaveBtn.textContent = 'Enregistrement...';
|
||||||
|
try {
|
||||||
|
const value = getCompletionModalFormValue();
|
||||||
|
// send raw HTML; server sanitizes/caps it
|
||||||
|
const saved = await saveCompletionModal(value);
|
||||||
|
setCompletionModalForm(saved);
|
||||||
|
updateCompletionModalPreview(getCompletionModalFormValue());
|
||||||
|
showResponse('success', 'Modal 100% enregistré.');
|
||||||
|
} catch (err) {
|
||||||
|
showResponse('danger', err.message || 'Erreur réseau');
|
||||||
|
} finally {
|
||||||
|
completionModalSaveBtn.disabled = false;
|
||||||
|
completionModalSaveBtn.textContent = oldText;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (userResetForm && userResetEmailEl) {
|
if (userResetForm && userResetEmailEl) {
|
||||||
userResetForm.addEventListener('submit', async function (e) {
|
userResetForm.addEventListener('submit', async function (e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -514,6 +699,9 @@ window.onload = async function () {
|
|||||||
document.title = `Pool Oscars ${activeYear} - Admin`;
|
document.title = `Pool Oscars ${activeYear} - Admin`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hookModal100Inputs();
|
||||||
|
await loadCompletionModalIntoUi();
|
||||||
|
|
||||||
if (saveActiveYearBtn && activeYearInput) {
|
if (saveActiveYearBtn && activeYearInput) {
|
||||||
saveActiveYearBtn.addEventListener('click', async () => {
|
saveActiveYearBtn.addEventListener('click', async () => {
|
||||||
const y = parseYear(activeYearInput.value);
|
const y = parseYear(activeYearInput.value);
|
||||||
|
|||||||
Reference in New Issue
Block a user