From dc989d556764e55314e00c1afdfb53225c2f1738 Mon Sep 17 00:00:00 2001 From: georgegale228 Date: Fri, 14 Aug 2026 21:22:56 +0000 Subject: [PATCH 1/5] =?UTF-8?q?feat(feedback):=20cr=C3=A9ation=20du=20form?= =?UTF-8?q?ulaire=20de=20feedback?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/routers/router_2026.py | 11 +- app/static/2026/css/pages/feedback.css | 108 ++++++++++++++++ app/static/2026/js/feedback.js | 136 ++++++++++++++++++++ app/templates/2026/2026_feedback.html | 166 +++++++++++++++++++++++++ app/templates/2026/base.html | 5 + 5 files changed, 423 insertions(+), 3 deletions(-) create mode 100644 app/static/2026/css/pages/feedback.css create mode 100644 app/static/2026/js/feedback.js create mode 100644 app/templates/2026/2026_feedback.html diff --git a/app/routers/router_2026.py b/app/routers/router_2026.py index 8e761a5..b5b804d 100644 --- a/app/routers/router_2026.py +++ b/app/routers/router_2026.py @@ -1668,9 +1668,14 @@ def volunteers(request: Request): @router.get("/feedback") -def feedback(request: Request): - # TODO - create a feedback page with a form to submit feedback - pass +async def feedback(request: Request): + return await _render_page_with_event( + request=request, + name="2026_feedback.html", + active_page="feedback", + page_css="feedback.css", + page_title="PyCon Togo 2026 - Feedback", + ) @router.get("/shop") diff --git a/app/static/2026/css/pages/feedback.css b/app/static/2026/css/pages/feedback.css new file mode 100644 index 0000000..bbb8e75 --- /dev/null +++ b/app/static/2026/css/pages/feedback.css @@ -0,0 +1,108 @@ +.feedback-card { + max-width: 820px; + margin: 0 auto; + background: #fff; + border-radius: 16px; + padding: 32px; + box-shadow: 0 4px 24px rgba(0,0,0,0.06); +} +.feedback-header { margin-bottom: 24px; } +.feedback-title { + color: #056d1e; + font-size: 1.75rem; + font-weight: 700; + margin: 0 0 8px; +} +.feedback-subtitle { + color: #444; + font-size: 1rem; + margin: 0; + line-height: 1.5; +} +.feedback-grid { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; +} +.feedback-full { grid-column: 1 / -1; } +.feedback-field { display: flex; flex-direction: column; gap: 6px; } +.feedback-label { + font-weight: 600; + color: #2D5016; + font-size: 0.9rem; + margin-bottom: 4px; +} +.feedback-form input[type="text"], +.feedback-form input[type="number"], +.feedback-form select, +.feedback-form textarea { + width: 100%; + padding: 10px 12px; + border-radius: 8px; + border: 1px solid rgba(0,0,0,0.08); + background: #ffffff; + color: #111; + font-size: 0.95rem; + font-family: inherit; +} +.feedback-form textarea { min-height: 100px; resize: vertical; } +.feedback-radio-group { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } +.feedback-radio-item { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + border-radius: 999px; + background: rgba(5,109,30,0.04); + border: 1px solid rgba(0,0,0,0.05); + color: #056d1e; + font-size: 0.9rem; + cursor: pointer; +} +.feedback-radio-item input { margin-right: 4px; } +.feedback-checkbox-group { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } +.feedback-checkbox-item { + display: inline-flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + border-radius: 999px; + background: rgba(5,109,30,0.04); + border: 1px solid rgba(0,0,0,0.05); + color: #056d1e; + font-size: 0.9rem; + cursor: pointer; +} +.feedback-checkbox-item input { margin-right: 4px; } +.feedback-actions { + display: flex; + gap: 12px; + align-items: center; + margin-top: 20px; + grid-column: 1 / -1; +} +.feedback-status { + grid-column: 1 / -1; + margin-top: 16px; + padding: 12px 16px; + border-radius: 10px; + display: none; + align-items: center; + gap: 10px; + font-weight: 600; +} +.feedback-status.success { + background: rgba(5,109,30,0.06); + border: 2px solid rgba(255,212,59,0.18); + color: #2D5016; +} +.feedback-status.error { + background: rgba(255,0,0,0.04); + border: 2px solid rgba(255,0,0,0.12); + color: #800; +} +.feedback-status-msg { flex: 1; } +@media (max-width: 720px) { + .feedback-card { padding: 20px; } + .feedback-grid { grid-template-columns: 1fr; } +} diff --git a/app/static/2026/js/feedback.js b/app/static/2026/js/feedback.js new file mode 100644 index 0000000..19d1773 --- /dev/null +++ b/app/static/2026/js/feedback.js @@ -0,0 +1,136 @@ +(function() { + "use strict"; + + var apiUrl = "https://api.pycontg.pytogo.org/api/feedback/"; + var form = document.getElementById("feedbackForm"); + var submitBtn = document.getElementById("submitBtn"); + var resetBtn = document.getElementById("resetBtn"); + var statusEl = document.getElementById("formStatus"); + var statusMsg = statusEl.querySelector(".feedback-status-msg"); + + var i18n = { + fr: { + required: "Veuillez remplir les champs requis.", + days_required: "Veuillez sélectionner au moins un jour.", + sending: "Envoi…", + success: "Merci ! Votre retour a été envoyé.", + error: "Une erreur est survenue. Veuillez réessayer plus tard.", + network: "Erreur réseau. Veuillez vérifier votre connexion." + }, + en: { + required: "Please fill in the required fields.", + days_required: "Please select at least one day.", + sending: "Sending…", + success: "Thank you! Your feedback has been sent.", + error: "An error occurred. Please try again later.", + network: "Network error. Please check your connection." + } + }; + + function getLang() { + return (window.currentLang || "fr").toLowerCase().startsWith("en") ? "en" : "fr"; + } + + function showStatus(success, text) { + statusEl.className = "feedback-status " + (success ? "success" : "error"); + statusMsg.textContent = text; + statusEl.style.display = "flex"; + } + + function hideStatus() { + statusEl.style.display = "none"; + statusMsg.textContent = ""; + } + + function setDaysError(show) { + var daysError = document.querySelector(".feedback-checkbox-error"); + if (!daysError) return; + daysError.style.display = show ? "block" : "none"; + } + + function daysSelected(fd) { + return fd.getAll("days").length > 0; + } + + var dayCheckboxes = document.querySelectorAll('input[type="checkbox"][name="days"]'); + dayCheckboxes.forEach(function(cb) { + cb.addEventListener("change", function() { + if (daysSelected(new FormData(form))) setDaysError(false); + }); + }); + + if (resetBtn) { + resetBtn.addEventListener("click", function() { + form.reset(); + setDaysError(false); + hideStatus(); + }); + } + + form.addEventListener("submit", async function(e) { + e.preventDefault(); + hideStatus(); + + var fd = new FormData(form); + var lang = getLang(); + var t = i18n[lang] || i18n.en; + + var payload = { + sex: fd.get("sex") || null, + age: fd.get("age") || null, + profession: (fd.get("profession") || "").trim() || null, + country: (fd.get("country") || "").trim() || null, + python_level: fd.get("python_level") || null, + days: fd.getAll("days"), + heard: fd.get("heard") || null, + rating: fd.get("rating") ? Number(fd.get("rating")) : null, + overall: (fd.get("overall") || "").trim() || null, + favorite: (fd.get("favorite") || "").trim() || null, + improvements: (fd.get("improvements") || "").trim() || null, + comments: (fd.get("comments") || "").trim() || null + }; + + if (!payload.days || payload.days.length === 0) { + setDaysError(true); + showStatus(false, t.days_required || t.required); + return; + } else { + setDaysError(false); + } + + if (!payload.sex && !payload.age && !payload.profession && !payload.heard && !payload.overall && !payload.favorite) { + showStatus(false, t.required); + return; + } + + submitBtn.disabled = true; + var originalText = submitBtn.textContent; + submitBtn.textContent = t.sending; + + try { + var res = await fetch(apiUrl, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(payload) + }); + + if (res.ok) { + showStatus(true, t.success); + form.reset(); + setDaysError(false); + } else { + var msg = t.error; + try { + var data = await res.json(); + if (data && data.message) msg = data.message + " - " + msg; + } catch (err) {} + showStatus(false, msg); + } + } catch (err) { + showStatus(false, t.network); + } finally { + submitBtn.disabled = false; + submitBtn.textContent = originalText; + } + }); +})(); diff --git a/app/templates/2026/2026_feedback.html b/app/templates/2026/2026_feedback.html new file mode 100644 index 0000000..c4acb20 --- /dev/null +++ b/app/templates/2026/2026_feedback.html @@ -0,0 +1,166 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+ +
+
+
+ +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/app/templates/2026/base.html b/app/templates/2026/base.html index 1e1d11e..6430905 100644 --- a/app/templates/2026/base.html +++ b/app/templates/2026/base.html @@ -76,6 +76,8 @@ data-i18n-fr="Speakers" href="/speakers">Speakers Schedule + Feedback diff --git a/app/templates/2026/base.html b/app/templates/2026/base.html index 6430905..a48b196 100644 --- a/app/templates/2026/base.html +++ b/app/templates/2026/base.html @@ -42,7 +42,7 @@ Home @@ -76,8 +78,6 @@ data-i18n-fr="Speakers" href="/speakers">Speakers Schedule - Feedback
@@ -176,8 +179,6 @@ onclick="closeMenu()">Our Speakers Schedule - Feedback
Register → @@ -249,6 +250,7 @@
Navigation
Sponsors Team Job Board + Feedback Contact
-
- 0 inscriptions +
+
+ 0 inscriptions +
+
From c01e6ec8072e9077bbe78c0c41959d1b4f415a14 Mon Sep 17 00:00:00 2001 From: georgegale228 Date: Mon, 24 Aug 2026 20:14:16 +0000 Subject: [PATCH 5/5] correction de l'affichage du login admin --- app/static/2026/css/pages/admin-login.css | 160 ++++++++++++++++++ app/templates/2026/2026_admin_login.html | 47 +++-- .../2026/2026_admin_registrations.html | 1 + 3 files changed, 183 insertions(+), 25 deletions(-) create mode 100644 app/static/2026/css/pages/admin-login.css diff --git a/app/static/2026/css/pages/admin-login.css b/app/static/2026/css/pages/admin-login.css new file mode 100644 index 0000000..a6511cb --- /dev/null +++ b/app/static/2026/css/pages/admin-login.css @@ -0,0 +1,160 @@ +/* ═══════════════════════════════════════════════ + PyCon Togo 2026 - Admin Login + ═══════════════════════════════════════════════ */ + +.admin-login-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + background: linear-gradient(135deg, var(--green-pale) 0%, var(--cream) 50%, var(--gold-pale) 100%); +} + +.admin-login-card { + width: 100%; + max-width: 420px; + background: var(--white); + border-radius: var(--radius-lg); + box-shadow: var(--shadow-xl); + padding: 40px 36px; + border: 1px solid var(--border); +} + +.admin-login-header { + text-align: center; + margin-bottom: 28px; +} + +.admin-login-logo { + width: 56px; + height: 56px; + border-radius: var(--radius); + background: var(--green); + display: inline-flex; + align-items: center; + justify-content: center; + margin-bottom: 16px; + color: var(--white); + font-weight: 800; + font-size: 1.25rem; + letter-spacing: -0.02em; +} + +.admin-login-title { + font-size: 1.5rem; + font-weight: 700; + color: var(--text); + margin: 0 0 6px; + letter-spacing: -0.01em; +} + +.admin-login-subtitle { + font-size: 0.95rem; + color: var(--text-muted); + margin: 0; + line-height: 1.5; +} + +.admin-login-form { + display: flex; + flex-direction: column; + gap: 18px; +} + +.admin-field { + display: flex; + flex-direction: column; + gap: 6px; +} + +.admin-field label { + font-size: 0.85rem; + font-weight: 600; + color: var(--text-mid); + letter-spacing: 0.02em; + text-transform: uppercase; +} + +.admin-field input { + width: 100%; + padding: 12px 14px; + border: 1.5px solid var(--border); + border-radius: var(--radius-sm); + background: var(--off-white); + color: var(--text); + font-size: 1rem; + font-family: var(--font-body); + transition: border-color 0.2s ease, box-shadow 0.2s ease, background 0.2s ease; + outline: none; +} + +.admin-field input:hover { + border-color: var(--border-mid); + background: var(--white); +} + +.admin-field input:focus { + border-color: var(--green); + box-shadow: 0 0 0 3px var(--green-dim); + background: var(--white); +} + +.admin-login-actions { + margin-top: 6px; +} + +.admin-login-submit { + width: 100%; + padding: 13px 16px; + border-radius: var(--radius-sm); + background: var(--green); + color: var(--white); + font-weight: 600; + font-size: 1rem; + letter-spacing: 0.01em; + border: none; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.2s ease, background 0.2s ease; + box-shadow: var(--shadow-sm); +} + +.admin-login-submit:hover { + background: var(--green-mid); + box-shadow: var(--shadow-md); + transform: translateY(-1px); +} + +.admin-login-submit:active { + transform: translateY(0); + box-shadow: var(--shadow-sm); +} + +.admin-login-submit:disabled { + opacity: 0.7; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.admin-login-error { + margin-top: 4px; + padding: 10px 12px; + border-radius: var(--radius-sm); + background: var(--red-pale); + color: var(--red); + font-size: 0.9rem; + line-height: 1.4; + border: 1px solid rgba(206, 17, 38, 0.15); +} + +@media (max-width: 480px) { + .admin-login-card { + padding: 28px 20px; + border-radius: var(--radius); + } + + .admin-login-title { + font-size: 1.25rem; + } +} diff --git a/app/templates/2026/2026_admin_login.html b/app/templates/2026/2026_admin_login.html index 713bbed..2245ffa 100644 --- a/app/templates/2026/2026_admin_login.html +++ b/app/templates/2026/2026_admin_login.html @@ -1,37 +1,34 @@ {% extends "base.html" %} {% block content %} -
-
-
-
+ + + +
+ {% endblock %} diff --git a/app/templates/2026/2026_admin_registrations.html b/app/templates/2026/2026_admin_registrations.html index 8eddc69..4134a03 100644 --- a/app/templates/2026/2026_admin_registrations.html +++ b/app/templates/2026/2026_admin_registrations.html @@ -54,5 +54,6 @@

+ {% endblock %}