From b36b3f632579ddf22f465b793edbcb8c143f79d4 Mon Sep 17 00:00:00 2001 From: Ruslan Date: Thu, 14 May 2026 06:42:09 +0000 Subject: [PATCH] Add contact modal, success messages, form reset on open --- app/main.py | 52 +++++++++++ app/static/style.css | 69 +++++++++++++++ app/templates/login.html | 180 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 299 insertions(+), 2 deletions(-) diff --git a/app/main.py b/app/main.py index c18f045..19de240 100644 --- a/app/main.py +++ b/app/main.py @@ -487,6 +487,58 @@ async def request_access(request: Request, db: Session = Depends(get_db)): return {"ok": True} + + +@app.post("/api/contact") +async def contact_ruslan(request: Request): + import re as _re + try: + data = await request.json() + except Exception: + raise HTTPException(status_code=400, detail="Invalid JSON") + + name = str(data.get("name", "")).strip() + email = str(data.get("email", "")).strip() + phone = str(data.get("phone", "")).strip() + text = str(data.get("text", "")).strip() + + if not name or not email or not phone or not text: + raise HTTPException(status_code=422, detail="Заполните все обязательные поля") + if not _re.match(r"^[^\s@]+@[^\s@]+\.[^\s@]+$", email): + raise HTTPException(status_code=422, detail="Некорректный email") + if not _re.match(r"^[\+\d][\d\s\-\(\)]{6,18}$", phone): + raise HTTPException(status_code=422, detail="Некорректный номер телефона") + + divider = "━━━━━━━━━━━━━━━━━━━━━━" + msg = ( + f"🔔 *Сообщение через форму полигона*\n" + f"{divider}\n\n" + f"👤 *Имя:* {name}\n" + f"📧 *Email:* {email}\n" + f"📱 *Телефон:* {phone}\n\n" + f"💬 *Сообщение:*\n{text}" + ) + + if not TELEGRAM_BOT_TOKEN or not TELEGRAM_CHAT_ID: + log_event("telegram_not_configured") + return {"ok": True} + + try: + payload = _json.dumps({ + "chat_id": TELEGRAM_CHAT_ID, + "text": msg, + "parse_mode": "Markdown", + }).encode() + url = f"{TELEGRAM_API_URL}{TELEGRAM_BOT_TOKEN}/sendMessage" + req = _urllib_request.Request(url, data=payload, headers={"Content-Type": "application/json"}) + with _urllib_request.urlopen(req, timeout=10) as resp: + resp.read() + except Exception as e: + log_event("telegram_send_error", error=str(e)) + raise HTTPException(status_code=502, detail="Ошибка отправки") + + return {"ok": True} + @app.post("/login") def login( request: Request, diff --git a/app/static/style.css b/app/static/style.css index f0a0ad0..4ca4963 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1523,3 +1523,72 @@ button { box-shadow: 0 0 0 3px rgba(220, 70, 70, 0.15) !important; background: rgba(220, 70, 70, 0.05) !important; } + +/* Access modal success state */ +.am-success-msg { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + padding: 1.5rem 0.5rem 0.5rem; + gap: 0.6rem; +} +.am-success-icon { + width: 52px; + height: 52px; + border-radius: 50%; + background: linear-gradient(135deg, #1e7dc8, #1360a0); + color: #fff; + font-size: 1.6rem; + display: flex; + align-items: center; + justify-content: center; + box-shadow: 0 4px 18px rgba(20, 96, 160, 0.4); +} +.am-success-title { + font-size: 1.1rem; + font-weight: 700; + color: #e0f0ff; +} +.am-success-sub { + font-size: 0.88rem; + color: rgba(160, 205, 238, 0.75); + line-height: 1.5; +} +.am-success-sub strong { + color: rgba(200, 230, 255, 0.9); +} + +/* Textarea in access modal */ +.access-textarea { + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.12); + border-radius: 8px; + padding: 0.6rem 0.85rem; + color: #daeeff; + font-size: 0.92rem; + font-family: inherit; + outline: none; + resize: vertical; + min-height: 90px; + transition: border-color 0.15s, box-shadow 0.15s; +} +.access-textarea::placeholder { + color: rgba(120,170,210,0.35); +} +.access-textarea:focus { + border-color: rgba(42,130,210,0.55); + box-shadow: 0 0 0 3px rgba(42,130,210,0.12); +} +.access-textarea.am-invalid { + border-color: rgba(220, 70, 70, 0.7) !important; + box-shadow: 0 0 0 3px rgba(220, 70, 70, 0.15) !important; + background: rgba(220, 70, 70, 0.05) !important; +} + +/* Footer link as button */ +.login-footer-link { + background: none; + border: none; + cursor: pointer; +} diff --git a/app/templates/login.html b/app/templates/login.html index 9d4798c..3ec297b 100644 --- a/app/templates/login.html +++ b/app/templates/login.html @@ -74,7 +74,7 @@ @@ -130,7 +130,31 @@ const errEl = document.getElementById('am-error'); let productsLoaded = false; + function resetAccessForm() { + if (!document.getElementById('am-name')) { + document.querySelector('.access-modal-body').innerHTML = ` +
+
+
+
+
+
Загрузка...
+ `; + document.querySelector('.access-modal-footer').innerHTML = ``; + document.getElementById('am-cancel').addEventListener('click', closeModal); + document.getElementById('am-submit').addEventListener('click', submitForm); + document.querySelectorAll('#am-name,#am-company,#am-email,#am-phone').forEach(function(el){ el.addEventListener('input', function(){ el.classList.remove('am-invalid'); }); }); + productsLoaded = false; + } else { + ['am-name','am-company','am-email','am-phone','am-manager'].forEach(function(id){ var el=document.getElementById(id); if(el){el.value='';el.classList.remove('am-invalid');} }); + document.querySelectorAll('#am-products input[type=checkbox]').forEach(function(cb){ cb.checked=false; }); + var err=document.getElementById('am-error'); if(err) err.style.display='none'; + var btn=document.getElementById('am-submit'); if(btn){btn.disabled=false;btn.textContent='Запросить доступ';} + } + } + function openModal() { + resetAccessForm(); overlay.style.display = 'flex'; document.body.style.overflow = 'hidden'; if (!productsLoaded) loadProducts(); @@ -217,7 +241,15 @@ throw new Error(d.detail || 'Ошибка отправки'); } btnSubmit.textContent = 'Отправлено!'; - setTimeout(closeModal, 1500); + // Show success message + const body = document.querySelector('.access-modal-body'); + const footer = document.querySelector('.access-modal-footer'); + body.innerHTML = '
' + + '
' + + '
Запрос отправлен
' + + '
После утверждения доступы придут на электронную почту ' + email + '
' + + '
'; + footer.innerHTML = ''; } catch(e) { errEl.textContent = e.message || 'Ошибка отправки, попробуйте позже'; errEl.style.display = 'block'; @@ -249,5 +281,149 @@ }); })(); + + + + +