Add connection review page with optional message on connection requests
- New GET route and template for reviewing individual connection requests
- Optional personal message field when sending connection requests
- Message visible to recipient on review page and connection list
- Dashboard "Review" button links to per-connection review page
- QR scan flow shows message prompt before sending request
- Fix accepted connections query: include both sent and received
- Fix url_for('uploaded_file') -> url_for('static', 'uploads/')
- Fix request.json -> request.is_json to avoid 415 on form POST
- Remove card from DOM on approve/reject instead of page reload
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,13 @@
|
||||
<div class="result-icon" id="result-icon"></div>
|
||||
<h2 id="result-title"></h2>
|
||||
<p id="result-message"></p>
|
||||
<button id="scan-again" class="btn btn-primary"><i data-lucide="scan-line"></i> {{ 'scan_again'|t }}</button>
|
||||
<div id="result-actions" class="result-actions hidden">
|
||||
<textarea id="message-input" class="message-input" placeholder="{{ 'optional_message'|t }}" rows="2" maxlength="500"></textarea>
|
||||
<div class="result-buttons">
|
||||
<button id="send-request-btn" class="btn btn-primary"><i data-lucide="send"></i> {{ 'send_request'|t }}</button>
|
||||
<button id="scan-again" class="btn btn-outline">{{ 'scan_again'|t }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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 = '<i data-lucide="clock"></i>';
|
||||
resultActions.classList.add('hidden');
|
||||
|
||||
if (data.success) {
|
||||
document.getElementById('qr-reader').style.display = 'none';
|
||||
resultDiv.classList.remove('hidden');
|
||||
icon.classList.add('pending');
|
||||
icon.innerHTML = '<i data-lucide="clock"></i>';
|
||||
icon.classList.add('success');
|
||||
icon.innerHTML = '<i data-lucide="check-circle"></i>';
|
||||
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 = '<i data-lucide="message-circle"></i>';
|
||||
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);
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user