diff --git a/app.py b/app.py index e8bb278..4f423ff 100644 --- a/app.py +++ b/app.py @@ -192,6 +192,7 @@ ENGLISH_TRANSLATIONS = { 'time': 'Time', 'organisation': 'Organisation', 'role': 'Role', + 'introduction': 'Introduction', 'role_profession': 'Role / Profession', 'phone': 'Phone', 'linkedin': 'LinkedIn', @@ -267,9 +268,20 @@ ENGLISH_TRANSLATIONS = { 'save_profile': 'Save Profile', 'profile_updated': 'Profile updated successfully', 'connection_requests': 'Connection Requests', + 'connection_requests_sent': 'Connection Requests Sent', + 'connection_request_pending': 'Connection Request Pending', 'my_connections': 'My Connections', 'send_connection_request': 'Send Connection Request', 'connection_sent': 'Connection request sent', + 'review': 'Review', + 'review_connection_request': 'Review Connection Request', + 'back_to_connection_requests': 'Back to Connection Requests', + 'request_pending': 'Request Pending', + 'connected': 'Connected', + 'incoming_connection_requests': 'Incoming Connection Requests', + 'no_connections_yet': 'No connections yet.', + 'find_attendees': 'Find Attendees', + 'view_all_incoming_requests': 'View all incoming requests', 'accept': 'Accept', 'reject': 'Reject', 'pending': 'Pending', @@ -411,6 +423,11 @@ ENGLISH_TRANSLATIONS = { 'specific_staff': 'Specific Staff', 'select_recipients': 'Select Recipients', 'message': 'Message', + 'optional_message': 'Add a personal message (optional)', + 'message_placeholder': 'Would be great to connect...', + 'send_request': 'Send Request', + 'scanned_successfully': 'Scanned Successfully', + 'scan_add_message_hint': 'Add an optional message to your connection request.', 'send_email': 'Send Email', 'edit_template': 'Edit Template', 'subject': 'Subject', @@ -5042,16 +5059,21 @@ def attendee_dashboard(): cursor.execute("SELECT * FROM attendees WHERE id = %s", (session['user_id'],)) attendee = cursor.fetchone() - # Get connections + # Get accepted connections (both sent and received) cursor.execute(""" SELECT c.*, a.first_name, a.last_name, a.organisation, a.role FROM connections c JOIN attendees a ON c.connected_attendee_id = a.id WHERE c.attendee_id = %s AND c.status = 'accepted' - """, (session['user_id'],)) + UNION + SELECT c.*, a.first_name, a.last_name, a.organisation, a.role + FROM connections c + JOIN attendees a ON c.attendee_id = a.id + WHERE c.connected_attendee_id = %s AND c.status = 'accepted' + """, (session['user_id'], session['user_id'])) connections = cursor.fetchall() - # Get pending connections + # Get pending connections (incoming requests from others) cursor.execute(""" SELECT c.*, a.first_name, a.last_name, a.organisation, a.role FROM connections c @@ -5060,6 +5082,15 @@ def attendee_dashboard(): """, (session['user_id'],)) pending_connections = cursor.fetchall() + # Get outgoing pending connections (requests sent by this attendee) + cursor.execute(""" + SELECT c.*, a.first_name, a.last_name, a.organisation, a.role + FROM connections c + JOIN attendees a ON c.connected_attendee_id = a.id + WHERE c.attendee_id = %s AND c.status = 'pending' + """, (session['user_id'],)) + sent_pending_connections = cursor.fetchall() + # Get appointments cursor.execute(""" SELECT ap.*, a.first_name as requester_first_name, a.last_name as requester_last_name, @@ -5083,6 +5114,7 @@ def attendee_dashboard(): return render_template('attendee/dashboard.html', event=event, attendee=attendee, connections=connections, pending_connections=pending_connections, + sent_pending_connections=sent_pending_connections, appointments=appointments) except Error as e: logging.error(f'Database error: {e}') @@ -5443,20 +5475,31 @@ def list_attendees(): event = cursor.fetchone() if len(search_query) >= min_chars: - # Search in first_name, last_name, and organisation - search_pattern = f'%{search_query}%' - cursor.execute(""" + # Split into words, search each word across first_name, last_name, and organisation (AND logic) + words = search_query.split() + word_conditions = [] + params = [session['user_id'], session['event_id'], session['user_id']] + + for word in words: + pattern = f'%{word}%' + word_conditions.append( + '(LOWER(a.first_name) LIKE LOWER(%s) OR LOWER(a.last_name) LIKE LOWER(%s) OR LOWER(a.organisation) LIKE LOWER(%s))' + ) + params.extend([pattern, pattern, pattern]) + + query = """ SELECT a.*, (SELECT status FROM connections WHERE attendee_id = %s AND connected_attendee_id = a.id) as my_status FROM attendees a WHERE a.event_id = %s AND a.id != %s AND a.visible_to_others = TRUE - AND (a.first_name LIKE %s OR a.last_name LIKE %s OR a.organisation LIKE %s) - """, (session['user_id'], session['event_id'], session['user_id'], search_pattern, search_pattern, search_pattern)) + AND (""" + ' AND '.join(word_conditions) + ')' + + cursor.execute(query, tuple(params)) attendees = cursor.fetchall() - # Check if more than 2 attendees found - if len(attendees) > 2: + # Check if more than 5 attendees found + if len(attendees) > 5: too_broad = True attendees = [] else: @@ -5487,6 +5530,8 @@ def create_connection(): if not connected_id: return jsonify({'error': 'Attendee ID required'}), 400 + message = request.form.get('message', '').strip() or None + try: conn = get_db_connection() cursor = conn.cursor() @@ -5502,9 +5547,9 @@ def create_connection(): return jsonify({'error': 'Connection already exists'}), 400 cursor.execute(""" - INSERT INTO connections (attendee_id, connected_attendee_id, status) - VALUES (%s, %s, 'pending') - """, (session['user_id'], connected_id)) + INSERT INTO connections (attendee_id, connected_attendee_id, status, message) + VALUES (%s, %s, 'pending', %s) + """, (session['user_id'], connected_id, message)) conn.commit() cursor.close() conn.close() @@ -5570,6 +5615,7 @@ def scan_request_connection(): if not scanned_id: return jsonify({'error': 'Scanned attendee ID required'}), 400 + message = (request.json.get('message', '') if request.json else request.form.get('message', '')).strip() or None scanned_id = int(scanned_id) # Can't connect to yourself @@ -5608,9 +5654,9 @@ def scan_request_connection(): else: # Rejected - allow new request cursor.execute(""" - UPDATE connections SET status = 'pending', attendee_id = %s, connected_attendee_id = %s + UPDATE connections SET status = 'pending', attendee_id = %s, connected_attendee_id = %s, message = %s WHERE id = %s - """, (session['user_id'], scanned_id, existing['id'])) + """, (session['user_id'], scanned_id, message, existing['id'])) conn.commit() cursor.close() conn.close() @@ -5618,9 +5664,9 @@ def scan_request_connection(): else: # Create new connection request cursor.execute(""" - INSERT INTO connections (attendee_id, connected_attendee_id, status) - VALUES (%s, %s, 'pending') - """, (session['user_id'], scanned_id)) + INSERT INTO connections (attendee_id, connected_attendee_id, status, message) + VALUES (%s, %s, 'pending', %s) + """, (session['user_id'], scanned_id, message)) conn.commit() cursor.close() conn.close() @@ -5664,25 +5710,22 @@ def connection_requests(): -@app.route('/attendee/connection-request/', methods=['POST']) +@app.route('/attendee/connection-request/', methods=['GET', 'POST']) @login_required def respond_to_connection(connection_id): - """Approve or reject a connection request.""" + """Show connection request details and handle approve/reject.""" if session.get('user_type') != 'attendee': - return jsonify({'error': 'Access denied'}), 403 - - action = request.json.get('action') if request.json else request.form.get('action') - if action not in ['approve', 'reject']: - return jsonify({'error': 'Invalid action'}), 400 + flash('Access denied.') + return redirect(url_for('index')) try: conn = get_db_connection() cursor = conn.cursor(dictionary=True) - # Verify this request is for the current user + # Fetch the connection request cursor.execute(""" SELECT c.*, a.first_name, a.last_name, a.profile_picture, - a.email, a.organisation, a.role, a.introduction + a.email, a.organisation, a.role, a.introduction, a.phone, a.linkedin FROM connections c JOIN attendees a ON c.attendee_id = a.id WHERE c.id = %s AND c.connected_attendee_id = %s AND c.status = 'pending' @@ -5692,23 +5735,44 @@ def respond_to_connection(connection_id): if not connection: cursor.close() conn.close() - return jsonify({'error': 'Connection request not found'}), 404 + flash('Connection request not found.') + return redirect(url_for('connection_requests')) + + # GET request: show the review page + if request.method == 'GET': + cursor.close() + conn.close() + return render_template('attendee/connection_review.html', connection=connection) + + # POST request: approve or reject + if request.is_json: + data = request.get_json(silent=True) or {} + action = data.get('action') + else: + action = request.form.get('action') + if action not in ['approve', 'reject']: + cursor.close() + conn.close() + flash('Invalid action.') + return redirect(url_for('respond_to_connection', connection_id=connection_id)) if action == 'approve': cursor.execute("UPDATE connections SET status = 'accepted' WHERE id = %s", (connection_id,)) - conn.commit() - cursor.close() - conn.close() - return jsonify({'success': True, 'status': 'accepted'}) else: cursor.execute("UPDATE connections SET status = 'rejected' WHERE id = %s", (connection_id,)) - conn.commit() - cursor.close() - conn.close() - return jsonify({'success': True, 'status': 'rejected'}) + conn.commit() + cursor.close() + conn.close() + + if request.is_json: + return jsonify({'success': True, 'status': 'accepted' if action == 'approve' else 'rejected'}) + flash(f"Connection request {action}d.") + return redirect(url_for('connection_requests')) except Error as e: - return jsonify({'error': str(e)}), 500 + logging.error(f'Database error: {e}') + flash('An unexpected error occurred. Please try again.') + return redirect(url_for('attendee_dashboard')) # Routes - Appointments diff --git a/init_db.py b/init_db.py index 580c512..a2a85c9 100644 --- a/init_db.py +++ b/init_db.py @@ -257,6 +257,7 @@ def create_tables(): "ALTER TABLE staff ADD COLUMN reminder_count INT DEFAULT 0", "ALTER TABLE staff ADD COLUMN reminder_dates JSON DEFAULT NULL", "ALTER TABLE breakout_session_organizers ADD COLUMN presenter_code VARCHAR(10) UNIQUE DEFAULT NULL", + "ALTER TABLE connections ADD COLUMN message TEXT DEFAULT NULL", ] try: diff --git a/static/css/style.css b/static/css/style.css index d7364ff..5f3c7b4 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -2533,6 +2533,269 @@ select:focus { margin-top: var(--space-6); } +/* ----- Connection Review Page ----- */ +.breadcrumb-nav { + margin-bottom: var(--space-4); + font-size: var(--font-size-sm); + color: var(--text-muted); +} + +.breadcrumb-nav a { + color: var(--secondary-color); + text-decoration: none; +} + +.breadcrumb-nav a:hover { + text-decoration: underline; +} + +.breadcrumb-nav .separator { + margin: 0 var(--space-2); +} + +.breadcrumb-nav .current { + color: var(--text-color); +} + +.review-card { + background: var(--card-bg); + border: 1px solid var(--border-color); + border-radius: var(--radius-xl); + box-shadow: var(--shadow-md); + overflow: hidden; +} + +.review-header { + display: flex; + align-items: center; + gap: var(--space-5); + padding: var(--space-8) var(--space-8) var(--space-4); + background: var(--bg-color); + border-bottom: 1px solid var(--border-color); +} + +.review-photo { + width: 120px; + height: 120px; + border-radius: 50%; + object-fit: cover; + border: 4px solid var(--border-color); +} + +.review-photo-placeholder { + width: 120px; + height: 120px; + border-radius: 50%; + background: var(--bg-color); + display: flex; + align-items: center; + justify-content: center; + font-size: var(--font-size-3xl); + font-weight: 700; + color: var(--text-muted); + border: 4px solid var(--border-color); + flex-shrink: 0; +} + +.review-name-org h2 { + margin: 0 0 var(--space-1) 0; + font-size: var(--font-size-2xl); +} + +.review-title { + color: var(--secondary-color); + margin: 0; + font-size: var(--font-size-lg); +} + +.review-details { + padding: var(--space-6) var(--space-8); +} + +.detail-row { + display: flex; + padding: var(--space-3) 0; + border-bottom: 1px solid var(--border-light, #f0f0f0); +} + +.detail-row:last-child { + border-bottom: none; +} + +.detail-label { + width: 140px; + flex-shrink: 0; + font-weight: 600; + color: var(--text-muted); + font-size: var(--font-size-sm); + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.detail-value { + flex: 1; + color: var(--text-color); + word-break: break-word; +} + +.detail-intro { + align-items: flex-start; +} + +.intro-text { + font-style: italic; + color: var(--text-muted); + padding-left: var(--space-3); + border-left: 3px solid var(--border-color); +} + +.message-text { + font-style: italic; + color: var(--text-color); + padding: var(--space-3); + background: var(--bg-highlight, #f0f7ff); + border-radius: var(--radius-md); + border-left: 3px solid var(--primary-color); +} + +.detail-message .detail-label { + color: var(--primary-color); +} + +/* ----- Connect Form (attendees list) ----- */ +.connect-form.hidden { + display: none; +} + +.connect-message-input { + width: 100%; + padding: var(--space-2) var(--space-3); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-size: var(--font-size-sm); + resize: vertical; + min-height: 40px; + font-family: inherit; + margin-bottom: var(--space-2); +} + +.connect-message-input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px var(--primary-color-alpha, rgba(67, 97, 238, 0.15)); +} + +.connect-form-actions { + display: flex; + gap: var(--space-2); +} + +.btn-ghost { + background: transparent; + color: var(--text-muted); + border: 1px solid transparent; + padding: var(--space-1) var(--space-3); + border-radius: var(--radius-md); + cursor: pointer; + font-size: var(--font-size-sm); +} + +.btn-ghost:hover { + background: var(--bg-color); + color: var(--text-color); +} + +/* ----- Request Message (list page) ----- */ +.request-message { + font-style: italic; + color: var(--text-color); + margin: 0 0 var(--space-2) 0; + font-size: var(--font-size-sm); + padding: var(--space-2) var(--space-3); + background: var(--bg-highlight, #f0f7ff); + border-radius: var(--radius-md); + border-left: 3px solid var(--primary-color); +} + +/* ----- Scan Result Actions ----- */ +.result-actions { + margin-top: var(--space-4); +} + +.message-input { + width: 100%; + padding: var(--space-3); + border: 1px solid var(--border-color); + border-radius: var(--radius-md); + font-size: var(--font-size-base); + resize: vertical; + min-height: 48px; + font-family: inherit; + margin-bottom: var(--space-3); + box-sizing: border-box; +} + +.message-input:focus { + outline: none; + border-color: var(--primary-color); + box-shadow: 0 0 0 2px var(--primary-color-alpha, rgba(67, 97, 238, 0.15)); +} + +.result-buttons { + display: flex; + gap: var(--space-3); + justify-content: center; +} + +.review-actions { + display: flex; + gap: var(--space-4); + padding: var(--space-6) var(--space-8); + background: var(--bg-color); + border-top: 1px solid var(--border-color); + justify-content: center; +} + +.review-actions .btn-lg { + padding: var(--space-3) var(--space-8); + font-size: var(--font-size-lg); + min-width: 160px; +} + +.review-form { + display: inline; +} + +@media (max-width: 768px) { + .review-header { + flex-direction: column; + text-align: center; + padding: var(--space-6) var(--space-4) var(--space-4); + } + + .review-details { + padding: var(--space-4); + } + + .detail-row { + flex-direction: column; + gap: var(--space-1); + } + + .detail-label { + width: 100%; + } + + .review-actions { + flex-direction: column; + padding: var(--space-4); + } + + .review-actions .btn-lg { + width: 100%; + } +} + /* ----- Attendee Dashboard ----- */ .attendee-dashboard { padding: var(--space-6) 0; diff --git a/templates/attendee/attendees.html b/templates/attendee/attendees.html index 50d72d7..6df563d 100644 --- a/templates/attendee/attendees.html +++ b/templates/attendee/attendees.html @@ -61,9 +61,14 @@ {% elif att.my_status == 'rejected' %} {{ 'rejected'|t }} {% else %} -
+ + - + +
+ + +
{% endif %} @@ -75,4 +80,24 @@ {% endif %} {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} + +{% block scripts %} + +{% endblock %} diff --git a/templates/attendee/connection_requests.html b/templates/attendee/connection_requests.html index 24a2a33..a30091c 100644 --- a/templates/attendee/connection_requests.html +++ b/templates/attendee/connection_requests.html @@ -23,7 +23,7 @@
{% if req.profile_picture %} - {{ req.first_name }} + {{ req.first_name }} {% else %}
{{ req.first_name[0] }}{{ req.last_name[0] }}
{% endif %} @@ -32,12 +32,16 @@

{{ req.first_name }} {{ req.last_name }}

{{ req.email }}

{{ req.organisation if req.organisation else '' }}{% if req.role %} - {{ req.role }}{% endif %}

+ {% if req.message %} +

"{{ req.message[:150] }}{% if req.message|length > 150 %}...{% endif %}"

+ {% endif %} {% if req.introduction %}

"{{ req.introduction }}"

{% endif %}

{{ 'requested'|t }} {{ req.created_at|localized_date if req.created_at else 'recently' }}

+ {{ 'review'|t }}
@@ -68,7 +72,11 @@ document.querySelectorAll('.respond-btn').forEach(btn => { const data = await response.json(); if (data.success) { - location.reload(); + this.closest('.request-card').remove(); + // If no requests left, reload to show the empty state + if (!document.querySelector('.request-card')) { + location.reload(); + } } else { showToast(data.error || 'Error processing request', 'error'); } diff --git a/templates/attendee/connection_review.html b/templates/attendee/connection_review.html new file mode 100644 index 0000000..a1bc262 --- /dev/null +++ b/templates/attendee/connection_review.html @@ -0,0 +1,89 @@ +{% extends "base.html" %} + +{% block title %}{{ 'review'|t }} - NetEvents{% endblock %} + +{% block content %} +
+ + +

{{ 'review_connection_request'|t }}

+ +
+
+
+ {% if connection.profile_picture %} + {{ connection.first_name }} + {% else %} +
{{ connection.first_name[0] if connection.first_name else '?' }}{{ connection.last_name[0] if connection.last_name else '' }}
+ {% endif %} +
+
+

{{ connection.first_name }} {{ connection.last_name }}

+ {% if connection.organisation or connection.role %} +

{{ connection.organisation }}{% if connection.organisation and connection.role %} - {% endif %}{{ connection.role }}

+ {% endif %} +
+
+ +
+
+ {{ 'email'|t }} + {{ connection.email }} +
+ {% if connection.phone %} +
+ {{ 'phone'|t }} + {{ connection.phone }} +
+ {% endif %} + {% if connection.linkedin %} +
+ LinkedIn + {{ connection.linkedin }} +
+ {% endif %} + {% if connection.message %} +
+ {{ 'message'|t }} + "{{ connection.message }}" +
+ {% endif %} + {% if connection.introduction %} +
+ {{ 'introduction'|t }} + "{{ connection.introduction }}" +
+ {% endif %} +
+ {{ 'requested'|t }} + {{ connection.created_at|localized_date if connection.created_at else '' }} +
+
+ +
+
+ + +
+
+ + +
+
+
+ + +
+{% endblock %} diff --git a/templates/attendee/dashboard.html b/templates/attendee/dashboard.html index 40a27c5..c8f4b84 100644 --- a/templates/attendee/dashboard.html +++ b/templates/attendee/dashboard.html @@ -29,10 +29,36 @@ {% endif %} + {% if sent_pending_connections %} +
+

{{ 'connection_requests_sent'|t }}

+
+ {% for conn in sent_pending_connections %} +
+

{{ conn.first_name }} {{ conn.last_name }}

+

{{ conn.organisation if conn.organisation else '' }}

+

{{ conn.role if conn.role else '' }}

+ {{ 'request_pending'|t }} +
+ {% endfor %} +
+
+ {% endif %} + {% if pending_connections %}

{{ 'incoming_connection_requests'|t }}

-

{{ 'review_scan_requests'|t }}

+
+ {% for conn in pending_connections %} +
+

{{ conn.first_name }} {{ conn.last_name }}

+

{{ conn.organisation if conn.organisation else '' }}

+

{{ conn.role if conn.role else '' }}

+ {{ 'review'|t }} +
+ {% endfor %} +
+

{{ 'view_all_incoming_requests'|t }}

{% endif %} @@ -49,6 +75,7 @@

{{ conn.first_name }} {{ conn.last_name }}

{{ conn.organisation if conn.organisation else '' }}

{{ conn.role if conn.role else '' }}

+ {{ 'connected'|t }}
{% endfor %} diff --git a/templates/attendee/scan.html b/templates/attendee/scan.html index cc987e6..9b80a6c 100644 --- a/templates/attendee/scan.html +++ b/templates/attendee/scan.html @@ -16,7 +16,13 @@

- + @@ -32,13 +38,14 @@ const myId = {{ session.user_id }}; const myEventId = {{ session.event_id }}; let html5QrCode; let scanning = true; +let pendingScannedId = null; -async function sendConnectionRequest(scannedId) { +async function sendConnectionRequest(scannedId, message) { try { const response = await fetch('/attendee/scan-request', { method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ scanned_id: scannedId }) + body: JSON.stringify({ scanned_id: scannedId, message: message }) }); const data = await response.json(); @@ -46,17 +53,19 @@ async function sendConnectionRequest(scannedId) { const icon = document.getElementById('result-icon'); const title = document.getElementById('result-title'); const message = document.getElementById('result-message'); + const resultActions = document.getElementById('result-actions'); icon.className = 'result-icon'; - icon.innerHTML = ''; + resultActions.classList.add('hidden'); if (data.success) { document.getElementById('qr-reader').style.display = 'none'; resultDiv.classList.remove('hidden'); - icon.classList.add('pending'); - icon.innerHTML = ''; + icon.classList.add('success'); + icon.innerHTML = ''; title.textContent = '{{ "request_sent"|t }}'; message.textContent = '{{ "request_sent_message"|t }}'; + document.getElementById('message-input').value = ''; } else { document.getElementById('qr-reader').style.display = 'none'; resultDiv.classList.remove('hidden'); @@ -68,6 +77,7 @@ async function sendConnectionRequest(scannedId) { lucide.createIcons(); scanning = false; + pendingScannedId = null; } catch (error) { console.error('Connection error:', error); showToast('Error sending connection request', 'error'); @@ -77,7 +87,6 @@ async function sendConnectionRequest(scannedId) { function onScanSuccess(decodedText) { if (!scanning) return; - // Expected format: "NETEVENT:{event_id}:{attendee_id}" const parts = decodedText.split(':'); if (parts.length === 3 && parts[0] === 'NETEVENT') { const eventId = parseInt(parts[1]); @@ -88,16 +97,54 @@ function onScanSuccess(decodedText) { showToast('{{ "cannot_scan_own_qr"|t }}', 'warning'); return; } - sendConnectionRequest(attendeeId); + pendingScannedId = attendeeId; + showMessagePrompt(); } else { showToast('{{ "qr_different_event"|t }}', 'error'); } } else { - // QR format not recognized showToast('{{ "unrecognized_qr"|t }}', 'error'); } } +function showMessagePrompt() { + const resultDiv = document.getElementById('scan-result'); + const icon = document.getElementById('result-icon'); + const title = document.getElementById('result-title'); + const msg = document.getElementById('result-message'); + const resultActions = document.getElementById('result-actions'); + + document.getElementById('qr-reader').style.display = 'none'; + resultDiv.classList.remove('hidden'); + resultActions.classList.remove('hidden'); + icon.className = 'result-icon pending'; + icon.innerHTML = ''; + title.textContent = '{{ "scanned_successfully"|t }}'; + msg.textContent = '{{ "scan_add_message_hint"|t }}'; + document.getElementById('message-input').value = ''; + document.getElementById('message-input').focus(); + + lucide.createIcons(); + scanning = false; +} + +document.getElementById('send-request-btn').addEventListener('click', function() { + if (pendingScannedId) { + const message = document.getElementById('message-input').value.trim(); + sendConnectionRequest(pendingScannedId, message || null); + } +}); + +document.getElementById('scan-again').addEventListener('click', resetScanner); + +function resetScanner() { + document.getElementById('qr-reader').style.display = 'block'; + document.getElementById('scan-result').classList.add('hidden'); + document.getElementById('message-input').value = ''; + scanning = true; + pendingScannedId = null; +} + function startScanner() { html5QrCode = new Html5Qrcode("qr-reader"); @@ -108,23 +155,13 @@ function startScanner() { qrbox: { width: 250, height: 250 } }, onScanSuccess, - (errorMessage) => { - // Ignore scan errors - } + (errorMessage) => {} ).catch(err => { console.error('Camera error:', err); showToast('{{ "camera_permission_error"|t }}', 'error'); }); } -function resetScanner() { - document.getElementById('qr-reader').style.display = 'block'; - document.getElementById('scan-result').classList.add('hidden'); - scanning = true; -} - -document.getElementById('scan-again').addEventListener('click', resetScanner); - window.addEventListener('DOMContentLoaded', startScanner); -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/templates/base.html b/templates/base.html index a44c7cd..32b6d15 100644 --- a/templates/base.html +++ b/templates/base.html @@ -99,5 +99,6 @@ {% block extra_styles %}{% endblock %} + {% block scripts %}{% endblock %} \ No newline at end of file