Implement QR-code service request system with Telegram bot integration

🎯 Key Features:
- Added QR-code generation for service requests in modal window
- Integrated real-time verification via Telegram bot
- Implemented animated success confirmation
- Added status polling for request verification

�� Technical Changes:
- Fixed JavaScript syntax errors in modern-scripts.js
- Enhanced services_modern.html with QR-code section and success animation
- Added check_request_status API endpoint
- Improved CSS with success checkmark animations

🎨 UX Improvements:
- Centered QR-code display with proper styling
- Real-time status checking every 3 seconds
- Animated success confirmation only after Telegram verification
- Auto-close modal after successful confirmation

📱 Workflow:
1. User fills service request form
2. QR-code generated and displayed
3. User scans QR/clicks Telegram link
4. System polls for verification status
5. Success animation shows after Telegram confirmation
6. Modal auto-closes with notification

This completes the modern service request system with Telegram bot integration.
This commit is contained in:
2025-11-23 22:16:52 +09:00
parent 1edb781a25
commit 76c326083f
7 changed files with 601 additions and 295 deletions

View File

@@ -51,7 +51,13 @@
<div class="col-lg-4 col-md-6 service-item" data-category="{{ service.category.name|lower }}">
<div class="card-modern h-100">
<div class="position-relative overflow-hidden" style="height: 200px;">
<img src="{{ service.image.url }}" class="w-100 h-100" style="object-fit: cover;" alt="{{ service.name }}">
{% if service.image %}
<img src="{{ service.image.url }}" class="w-100 h-100" style="object-fit: cover;" alt="{{ service.name }}">
{% else %}
<div class="w-100 h-100 bg-gradient d-flex align-items-center justify-content-center">
<i class="fas fa-cogs text-white" style="font-size: 3rem;"></i>
</div>
{% endif %}
<div class="position-absolute top-0 start-0 p-3">
<span class="badge bg-primary">{{ service.category.name }}</span>
</div>
@@ -296,6 +302,42 @@
</div>
</div>
<!-- QR Code Section (Hidden by default) -->
<div class="mt-4" id="qrCodeSection" style="display: none;">
<div class="alert alert-info text-center">
<h6><i class="fas fa-qrcode me-2"></i>Завершите регистрацию через Telegram</h6>
<p class="mb-3">Отсканируйте QR-код или перейдите по ссылке для подтверждения заявки:</p>
<div class="d-flex justify-content-center mb-3">
<img id="qrCodeImage" src="" alt="QR Code" class="img-fluid border rounded" style="max-width: 200px; min-width: 200px; height: 200px; object-fit: contain; display: none;">
</div>
<div class="mb-3">
<a id="telegramLink" href="" target="_blank" class="btn btn-info">
<i class="fab fa-telegram-plane me-2"></i>Открыть в Telegram
</a>
</div>
<div class="d-flex align-items-center justify-content-center">
<div class="spinner-border spinner-border-sm text-primary me-2" role="status" aria-hidden="true"></div>
<small class="text-muted">Ожидаем подтверждения в Telegram...</small>
</div>
</div>
</div>
<!-- Success Animation Section (Hidden by default) -->
<div class="mt-4" id="successSection" style="display: none;">
<div class="text-center py-5">
<div class="success-checkmark">
<div class="check-icon">
<span class="icon-line line-tip"></span>
<span class="icon-line line-long"></span>
<div class="icon-circle"></div>
<div class="icon-fix"></div>
</div>
</div>
<h4 class="text-success mt-3 mb-2">Заявка подана успешно!</h4>
<p class="text-muted">Мы свяжемся с вами в ближайшее время</p>
</div>
</div>
<div class="mt-4">
<div class="form-check">
<input class="form-check-input" type="checkbox" id="agreeTerms" required>
@@ -374,29 +416,108 @@ document.getElementById('serviceRequestForm').addEventListener('submit', functio
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin me-2"></i>Отправляем...';
submitBtn.disabled = true;
// Simulate form submission (replace with actual AJAX call)
setTimeout(() => {
submitBtn.innerHTML = '<i class="fas fa-check me-2"></i>Отправлено!';
submitBtn.classList.remove('btn-primary-modern');
submitBtn.classList.add('btn-success');
// Получаем CSRF токен
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
// Подготавливаем данные для отправки
const serviceId = document.getElementById('serviceId').value;
const requestData = {
client_name: formData.get('first_name') + ' ' + formData.get('last_name'),
client_email: formData.get('email'),
client_phone: formData.get('phone') || '',
description: formData.get('description'),
budget: formData.get('budget') || '',
timeline: formData.get('timeline') || ''
};
// Отправляем запрос на создание QR-кода
fetch(`/service/generate_qr_code/${serviceId}/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrfToken
},
body: JSON.stringify(requestData)
})
.then(response => response.json())
.then(data => {
if (data.qr_code_url) {
// Показываем секцию с QR-кодом
const qrSection = document.getElementById('qrCodeSection');
const qrImage = document.getElementById('qrCodeImage');
const telegramLink = document.getElementById('telegramLink');
qrImage.src = data.qr_code_url;
qrImage.style.display = 'block';
telegramLink.href = data.registration_link;
qrSection.style.display = 'block';
// Обновляем кнопку
submitBtn.innerHTML = '<i class="fas fa-telegram-plane me-2"></i>Перейдите в Telegram для подтверждения';
submitBtn.disabled = true;
// Начинаем проверку статуса подтверждения
const serviceRequestId = data.service_request_id;
confirmationCheckInterval = setInterval(() => {
checkConfirmationStatus(serviceRequestId, confirmationCheckInterval);
}, 3000); // Проверяем каждые 3 секунды
} else {
throw new Error(data.error || 'Ошибка при создании заявки');
}
})
.catch(error => {
console.error('Error:', error);
submitBtn.innerHTML = originalContent;
submitBtn.disabled = false;
submitBtn.classList.remove('btn-success');
submitBtn.classList.add('btn-primary-modern');
setTimeout(() => {
const modal = bootstrap.Modal.getInstance(document.getElementById('serviceModal'));
modal.hide();
// Reset form and button
this.reset();
submitBtn.innerHTML = originalContent;
submitBtn.disabled = false;
submitBtn.classList.remove('btn-success');
submitBtn.classList.add('btn-primary-modern');
// Show success notification
showNotification('Заявка отправлена! Мы свяжемся с вами в ближайшее время.', 'success');
}, 2000);
}, 2000);
showNotification('Произошла ошибка при создании заявки. Попробуйте еще раз.', 'error');
});
});
// Функция проверки статуса подтверждения
function checkConfirmationStatus(serviceRequestId, checkInterval) {
fetch(`/service/check_status/${serviceRequestId}/`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
})
.then(response => response.json())
.then(data => {
if (data.is_verified) {
// Останавливаем проверку
clearInterval(checkInterval);
// Скрываем QR-код
const qrSection = document.getElementById('qrCodeSection');
qrSection.style.display = 'none';
// Показываем анимацию успеха
const successSection = document.getElementById('successSection');
successSection.style.display = 'block';
// Запускаем анимацию галочки
setTimeout(() => {
const checkmark = successSection.querySelector('.success-checkmark');
checkmark.classList.add('animate');
}, 100);
// Закрываем модальное окно через 3 секунды
setTimeout(() => {
const modal = bootstrap.Modal.getInstance(document.getElementById('serviceModal'));
modal.hide();
showNotification('Заявка подтверждена! Спасибо за регистрацию в Telegram.', 'success');
}, 3000);
}
})
.catch(error => {
console.log('Ожидаем подтверждения...', error);
});
}
function showNotification(message, type) {
const notification = document.createElement('div');
notification.className = `alert alert-${type} position-fixed top-0 end-0 m-3`;
@@ -413,6 +534,38 @@ function showNotification(message, type) {
notification.remove();
}, 5000);
}
// Reset modal when it's hidden
let confirmationCheckInterval = null;
document.getElementById('serviceModal').addEventListener('hidden.bs.modal', function() {
// Останавливаем проверку подтверждения
if (confirmationCheckInterval) {
clearInterval(confirmationCheckInterval);
confirmationCheckInterval = null;
}
// Сброс формы
document.getElementById('serviceRequestForm').reset();
// Скрытие всех секций
document.getElementById('qrCodeSection').style.display = 'none';
document.getElementById('qrCodeImage').style.display = 'none';
document.getElementById('successSection').style.display = 'none';
// Убираем анимацию галочки
const checkmark = document.querySelector('.success-checkmark');
if (checkmark) {
checkmark.classList.remove('animate');
}
// Сброс кнопки отправки
const submitBtn = document.querySelector('button[type="submit"][form="serviceRequestForm"]');
submitBtn.innerHTML = '<i class="fas fa-paper-plane me-2"></i>Отправить заявку';
submitBtn.disabled = false;
submitBtn.classList.remove('btn-success');
submitBtn.classList.add('btn-primary-modern');
});
</script>
<style>

View File

@@ -14,6 +14,7 @@ urlpatterns = [
# path('create_order/<int:pk>/', views.create_order, name='create_order'),
path('about/', views.about_view, name="about"),
path('service/generate_qr_code/<int:service_id>/', views.generate_qr_code, name='generate_qr_code'),
path('service/check_status/<int:request_id>/', views.check_request_status, name='check_request_status'),
path('service/request_status/<int:service_id>/', views.request_status, name='request_status'),
path('service/request/<int:service_id>/', views.create_service_request, name='create_service_request'),
path('complete_registration/<int:request_id>/', views.complete_registration, name='complete_registration'),

View File

@@ -299,3 +299,19 @@ def complete_registration(request, request_id):
return JsonResponse({'status': 'success', 'message': 'Регистрация успешно завершена.'})
return render(request, 'web/complete_registration.html', {'service_request': service_request})
def check_request_status(request, request_id):
"""API endpoint для проверки статуса подтверждения заявки"""
try:
service_request = get_object_or_404(ServiceRequest, pk=request_id)
return JsonResponse({
'is_verified': service_request.is_verified,
'chat_id': service_request.chat_id,
'created_at': service_request.created_at.isoformat() if service_request.created_at else None
})
except ServiceRequest.DoesNotExist:
return JsonResponse({'error': 'Заявка не найдена'}, status=404)
except Exception as e:
logger.error(f"Ошибка при проверке статуса заявки {request_id}: {str(e)}")
return JsonResponse({'error': 'Ошибка сервера'}, status=500)