Add request access modal on login page with Telegram notification

- Modal form: name, company, email, phone (required), manager (optional), product checkboxes
- Products loaded from DB via GET /api/public/services-by-category (public route)
- POST /api/request-access sends styled Telegram message with divider and emojis
- Dark-themed modal matching login page design
- CSS: overlay, card, fields, checkbox list, error, footer buttons
This commit is contained in:
2026-05-14 06:22:39 +00:00
parent 9530f3e957
commit f740420a77
3 changed files with 419 additions and 1 deletions
+146 -1
View File
@@ -71,12 +71,157 @@
</div>
<button type="submit" class="login-submit">Войти</button>
</form>
<a class="login-request-btn" href="mailto:rgalyaviev@mont.com?subject=%D0%94%D0%BE%D1%81%D1%82%D1%83%D0%BF%20%D0%BA%20%D0%BF%D0%BE%D0%BB%D0%B8%D0%B3%D0%BE%D0%BD%D1%83">Запросить доступ</a>
<button type="button" class="login-request-btn" id="btn-request-access" data-open-access-modal="1">Запросить доступ</button>
</div>
<footer class="login-footer">
<a href="mailto:rgalyaviev@mont.com" class="login-footer-link">Made by Galyaviev</a>
</footer>
</main>
</div>
<!-- Request Access Modal -->
<div id="access-modal" class="access-modal-overlay" style="display:none" aria-modal="true" role="dialog">
<div class="access-modal">
<div class="access-modal-header">
<div class="access-modal-title">Запросить доступ</div>
<div class="access-modal-sub">Заполните форму — мы свяжемся с вами в ближайшее время</div>
</div>
<div class="access-modal-body">
<div class="access-field">
<label>Имя и фамилия <span class="req">*</span></label>
<input id="am-name" type="text" placeholder="Иван Иванов" />
</div>
<div class="access-field">
<label>Название компании <span class="req">*</span></label>
<input id="am-company" type="text" placeholder="ООО Компания" />
</div>
<div class="access-field">
<label>Email <span class="req">*</span></label>
<input id="am-email" type="email" placeholder="ivan@company.ru" />
</div>
<div class="access-field">
<label>Телефон <span class="req">*</span></label>
<input id="am-phone" type="tel" placeholder="+7 (999) 000-00-00" />
</div>
<div class="access-field">
<label>Ваш менеджер в МОНТ</label>
<input id="am-manager" type="text" placeholder="Если известно — укажите имя" />
</div>
<div class="access-field">
<label>Интересующие продукты</label>
<div id="am-products" class="access-products-wrap">
<div class="access-products-loading">Загрузка...</div>
</div>
</div>
<div id="am-error" class="access-modal-error" style="display:none"></div>
</div>
<div class="access-modal-footer">
<button type="button" class="access-btn-cancel" id="am-cancel">Отмена</button>
<button type="button" class="access-btn-submit" id="am-submit">Запросить доступ</button>
</div>
</div>
</div>
<script>
(function() {
const overlay = document.getElementById('access-modal');
const btnCancel = document.getElementById('am-cancel');
const btnSubmit = document.getElementById('am-submit');
const errEl = document.getElementById('am-error');
let productsLoaded = false;
function openModal() {
overlay.style.display = 'flex';
document.body.style.overflow = 'hidden';
if (!productsLoaded) loadProducts();
}
function closeModal() {
overlay.style.display = 'none';
document.body.style.overflow = '';
errEl.style.display = 'none';
}
async function loadProducts() {
const wrap = document.getElementById('am-products');
try {
const res = await fetch('/api/public/services-by-category');
const data = await res.json();
wrap.innerHTML = '';
for (const [cat, svcs] of Object.entries(data)) {
const group = document.createElement('div');
group.className = 'access-products-group';
group.innerHTML = '<div class="access-products-cat">' + cat + '</div>';
for (const svc of svcs) {
const lbl = document.createElement('label');
lbl.className = 'access-product-item';
lbl.innerHTML = '<input type="checkbox" value="' + svc.name.replace(/"/g, '&quot;') + '" /><span>' + svc.name + '</span>';
group.appendChild(lbl);
}
wrap.appendChild(group);
}
productsLoaded = true;
} catch(e) {
wrap.innerHTML = '<div class="access-products-loading">Не удалось загрузить список</div>';
}
}
async function submitForm() {
const name = document.getElementById('am-name').value.trim();
const company = document.getElementById('am-company').value.trim();
const email = document.getElementById('am-email').value.trim();
const phone = document.getElementById('am-phone').value.trim();
const manager = document.getElementById('am-manager').value.trim();
const checked = [...document.querySelectorAll('#am-products input[type=checkbox]:checked')];
const products = checked.map(c => c.value);
if (!name || !company || !email || !phone) {
errEl.textContent = 'Пожалуйста, заполните все обязательные поля';
errEl.style.display = 'block';
return;
}
btnSubmit.disabled = true;
btnSubmit.textContent = 'Отправка...';
errEl.style.display = 'none';
try {
const res = await fetch('/api/request-access', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({name, company, email, phone, manager, products}),
});
if (!res.ok) {
const d = await res.json().catch(() => ({}));
throw new Error(d.detail || 'Ошибка отправки');
}
btnSubmit.textContent = 'Отправлено!';
setTimeout(closeModal, 1500);
} catch(e) {
errEl.textContent = e.message || 'Ошибка отправки, попробуйте позже';
errEl.style.display = 'block';
btnSubmit.disabled = false;
btnSubmit.textContent = 'Запросить доступ';
}
}
// Wire up request-access button
document.querySelectorAll('.login-request-btn, [data-open-access-modal]').forEach(el => {
el.addEventListener('click', function(e) {
e.preventDefault();
openModal();
});
});
btnCancel.addEventListener('click', closeModal);
btnSubmit.addEventListener('click', submitForm);
overlay.addEventListener('click', function(e) {
if (e.target === overlay) closeModal();
});
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') closeModal();
});
})();
</script>
</body>
</html>