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:
@@ -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/<int:connection_id>', methods=['POST'])
|
||||
@app.route('/attendee/connection-request/<int:connection_id>', 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
|
||||
|
||||
Reference in New Issue
Block a user