Email plein ecran avec suggestions d'adresses precedentes

- Formulaire email : overlay fullscreen au lieu d'inline (debordait de l'ecran)
- Historique emails : stocke chaque adresse dans data/emails_history.json
- Suggestions : affiche les emails deja saisis en pills cliquables,
  filtrees au fur et a mesure de la saisie
- API GET /api/emails/historique + DELETE pour reset

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-22 18:57:28 +02:00
parent e730b6bc8b
commit 0b7b55f57b
5 changed files with 108 additions and 7 deletions

View File

@@ -1,3 +1,4 @@
import json
import logging
import smtplib
from email.mime.base import MIMEBase
@@ -6,7 +7,7 @@ from email.mime.text import MIMEText
from email import encoders
from pathlib import Path
from backend.config import charger_config
from backend.config import charger_config, RACINE
log = logging.getLogger("photobooth.mailer")
@@ -58,7 +59,38 @@ def envoyer_photo(destinataire: str, chemin_photo: Path) -> bool:
serveur.login(smtp_user, smtp_password)
serveur.send_message(msg)
log.info(f"Email envoye a {destinataire}")
_sauvegarder_email_historique(destinataire)
return True
except (smtplib.SMTPException, OSError) as e:
log.error(f"Erreur envoi email : {e}")
return False
FICHIER_EMAILS = RACINE / "data" / "emails_history.json"
def _sauvegarder_email_historique(email: str):
historique = charger_emails_historique()
email_lower = email.lower().strip()
if email_lower not in historique:
historique.append(email_lower)
try:
with open(FICHIER_EMAILS, "w", encoding="utf-8") as f:
json.dump(historique, f, ensure_ascii=False)
except OSError:
pass
def charger_emails_historique() -> list:
if not FICHIER_EMAILS.exists():
return []
try:
with open(FICHIER_EMAILS, "r", encoding="utf-8") as f:
return json.load(f)
except (json.JSONDecodeError, OSError):
return []
def effacer_emails_historique():
if FICHIER_EMAILS.exists():
FICHIER_EMAILS.unlink()

View File

@@ -28,7 +28,7 @@ from backend.effects import appliquer_filtre, appliquer_overlay, chroma_key, lis
from backend.collage import creer_strip, creer_collage
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur, recuperer_booth_password
from backend.printer import lister_imprimantes, imprimer
from backend.mailer import envoyer_photo
from backend.mailer import envoyer_photo, charger_emails_historique, effacer_emails_historique
from backend.qrcode_gen import generer_qr, qr_galerie
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
@@ -777,6 +777,17 @@ async def api_email(donnees: dict):
return {"succes": ok}
@app.get("/api/emails/historique")
async def api_emails_historique():
return {"emails": charger_emails_historique()}
@app.delete("/api/emails/historique")
async def api_emails_historique_supprimer():
effacer_emails_historique()
return {"succes": True}
# --- API QR Code ---
@app.get("/api/qr")

View File

@@ -674,10 +674,16 @@ html, body {
}
.form-email {
position: fixed;
inset: 0;
z-index: 200;
background: var(--fond);
display: flex;
gap: 0.8rem;
flex-direction: column;
align-items: center;
margin: 1rem 0;
justify-content: center;
gap: 0.5rem;
padding: 1rem;
}
.form-email input {
@@ -686,8 +692,9 @@ html, body {
border-radius: 12px;
padding: 0.8rem 1.2rem;
color: var(--texte);
font-size: 1.1rem;
width: 300px;
font-size: 1.2rem;
width: min(90vw, 500px);
text-align: center;
outline: none;
}
@@ -695,6 +702,30 @@ html, body {
border-color: var(--primaire);
}
.email-suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.4rem;
justify-content: center;
max-width: 90vw;
max-height: 4.5rem;
overflow-y: auto;
}
.email-suggestions button {
background: rgba(255,255,255,0.08);
border: 1px solid rgba(255,255,255,0.15);
border-radius: 20px;
color: var(--texte);
padding: 0.3rem 0.8rem;
font-size: 0.85rem;
cursor: pointer;
}
.email-suggestions button:active {
background: var(--primaire);
}
.zone-qr {
margin: 1rem 0;
}

View File

@@ -157,6 +157,7 @@
</div>
<div id="form-email" class="form-email cache">
<input type="email" id="input-email" placeholder="votre@email.com" autocomplete="off" readonly>
<div id="email-suggestions" class="email-suggestions"></div>
<div class="clavier-virtuel" id="clavier-email">
<div class="clavier-ligne">
<button onclick="clavierTape('1')">1</button>

View File

@@ -89,9 +89,10 @@ async function lancerImpression() {
}
}
function ouvrirEmail() {
async function ouvrirEmail() {
document.getElementById('form-email').classList.remove('cache');
document.getElementById('input-email').value = '';
await chargerSuggestionsEmail();
}
function fermerEmail() {
@@ -99,14 +100,39 @@ function fermerEmail() {
document.getElementById('input-email').value = '';
}
async function chargerSuggestionsEmail() {
const container = document.getElementById('email-suggestions');
container.innerHTML = '';
const data = await apiGet('/api/emails/historique').catch(() => null);
if (!data || !data.emails || data.emails.length === 0) return;
for (const email of data.emails) {
const btn = document.createElement('button');
btn.textContent = email;
btn.onclick = () => {
document.getElementById('input-email').value = email;
};
container.appendChild(btn);
}
}
function clavierTape(car) {
const input = document.getElementById('input-email');
input.value += car;
filtrerSuggestionsEmail();
}
function clavierEffacer() {
const input = document.getElementById('input-email');
input.value = input.value.slice(0, -1);
filtrerSuggestionsEmail();
}
function filtrerSuggestionsEmail() {
const val = document.getElementById('input-email').value.toLowerCase();
const btns = document.querySelectorAll('#email-suggestions button');
btns.forEach(btn => {
btn.style.display = (!val || btn.textContent.includes(val)) ? '' : 'none';
});
}
async function envoyerEmail() {