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:
@@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
import logging
|
import logging
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.mime.base import MIMEBase
|
from email.mime.base import MIMEBase
|
||||||
@@ -6,7 +7,7 @@ from email.mime.text import MIMEText
|
|||||||
from email import encoders
|
from email import encoders
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from backend.config import charger_config
|
from backend.config import charger_config, RACINE
|
||||||
|
|
||||||
log = logging.getLogger("photobooth.mailer")
|
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.login(smtp_user, smtp_password)
|
||||||
serveur.send_message(msg)
|
serveur.send_message(msg)
|
||||||
log.info(f"Email envoye a {destinataire}")
|
log.info(f"Email envoye a {destinataire}")
|
||||||
|
_sauvegarder_email_historique(destinataire)
|
||||||
return True
|
return True
|
||||||
except (smtplib.SMTPException, OSError) as e:
|
except (smtplib.SMTPException, OSError) as e:
|
||||||
log.error(f"Erreur envoi email : {e}")
|
log.error(f"Erreur envoi email : {e}")
|
||||||
return False
|
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()
|
||||||
|
|||||||
@@ -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.collage import creer_strip, creer_collage
|
||||||
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur, recuperer_booth_password
|
from backend.destinations import distribuer_photo, detecter_usb, compteur_restant, reset_compteur, recuperer_booth_password
|
||||||
from backend.printer import lister_imprimantes, imprimer
|
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
|
from backend.qrcode_gen import generer_qr, qr_galerie
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(name)s] %(message)s")
|
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}
|
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 ---
|
# --- API QR Code ---
|
||||||
|
|
||||||
@app.get("/api/qr")
|
@app.get("/api/qr")
|
||||||
|
|||||||
@@ -674,10 +674,16 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-email {
|
.form-email {
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 200;
|
||||||
|
background: var(--fond);
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.8rem;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 1rem 0;
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-email input {
|
.form-email input {
|
||||||
@@ -686,8 +692,9 @@ html, body {
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 0.8rem 1.2rem;
|
padding: 0.8rem 1.2rem;
|
||||||
color: var(--texte);
|
color: var(--texte);
|
||||||
font-size: 1.1rem;
|
font-size: 1.2rem;
|
||||||
width: 300px;
|
width: min(90vw, 500px);
|
||||||
|
text-align: center;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,6 +702,30 @@ html, body {
|
|||||||
border-color: var(--primaire);
|
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 {
|
.zone-qr {
|
||||||
margin: 1rem 0;
|
margin: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,6 +157,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="form-email" class="form-email cache">
|
<div id="form-email" class="form-email cache">
|
||||||
<input type="email" id="input-email" placeholder="votre@email.com" autocomplete="off" readonly>
|
<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-virtuel" id="clavier-email">
|
||||||
<div class="clavier-ligne">
|
<div class="clavier-ligne">
|
||||||
<button onclick="clavierTape('1')">1</button>
|
<button onclick="clavierTape('1')">1</button>
|
||||||
|
|||||||
@@ -89,9 +89,10 @@ async function lancerImpression() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function ouvrirEmail() {
|
async function ouvrirEmail() {
|
||||||
document.getElementById('form-email').classList.remove('cache');
|
document.getElementById('form-email').classList.remove('cache');
|
||||||
document.getElementById('input-email').value = '';
|
document.getElementById('input-email').value = '';
|
||||||
|
await chargerSuggestionsEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function fermerEmail() {
|
function fermerEmail() {
|
||||||
@@ -99,14 +100,39 @@ function fermerEmail() {
|
|||||||
document.getElementById('input-email').value = '';
|
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) {
|
function clavierTape(car) {
|
||||||
const input = document.getElementById('input-email');
|
const input = document.getElementById('input-email');
|
||||||
input.value += car;
|
input.value += car;
|
||||||
|
filtrerSuggestionsEmail();
|
||||||
}
|
}
|
||||||
|
|
||||||
function clavierEffacer() {
|
function clavierEffacer() {
|
||||||
const input = document.getElementById('input-email');
|
const input = document.getElementById('input-email');
|
||||||
input.value = input.value.slice(0, -1);
|
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() {
|
async function envoyerEmail() {
|
||||||
|
|||||||
Reference in New Issue
Block a user