diff --git a/src/anonchat/routes.py b/src/anonchat/routes.py index 4e91c09..395db0a 100644 --- a/src/anonchat/routes.py +++ b/src/anonchat/routes.py @@ -1,9 +1,10 @@ -from flask import request, jsonify, render_template, redirect, url_for, flash, session +from flask import request, jsonify, render_template, redirect, url_for, flash, session, make_response from . import app, db, limiter, csrf, webhooks from .webhooks import WebhookError from .models import Inquiry, Message, Settings, Admin import os -from datetime import datetime, timedelta +import json +from datetime import datetime, timedelta, timezone # Import admin routes (these will be registered with the app) from . import admin_routes @@ -13,7 +14,50 @@ from .admin_routes import is_admin @app.route('/', methods=['GET']) def index(): - return render_template('create_inquiry.html') + # Get recent inquiries from cookie + recent_inquiries = [] + read_status = {} + + if request.cookies.get('recent_inquiries'): + try: + inquiry_ids = json.loads(request.cookies.get('recent_inquiries')) + recent_inquiries = Inquiry.query.filter(Inquiry.id.in_(inquiry_ids)).all() + + # Get read status for each inquiry + if request.cookies.get('read_messages'): + read_status = json.loads(request.cookies.get('read_messages')) + except: + # If there's an error parsing the cookie, just ignore it + pass + + # Create a list to store inquiries with their data + inquiries_with_data = [] + + for inquiry_id in (inquiry_ids if 'inquiry_ids' in locals() else []): + for inquiry in recent_inquiries: + if inquiry.id == inquiry_id: + # Get last message for the inquiry + last_message = Message.query.filter_by(inquiry_id=inquiry.id).order_by(Message.message_number.desc()).first() + + # Check if there are unread messages + is_unread = False + if last_message: + last_read = read_status.get(inquiry.id, 0) + if last_message.message_number > last_read and last_message.is_admin: + is_unread = True + + inquiries_with_data.append({ + 'inquiry': inquiry, + 'last_message': last_message, + 'is_unread': is_unread, + 'position': inquiry_ids.index(inquiry.id) # Keep track of original position + }) + break + + # Sort inquiries - unread first, then by original position (recency) + inquiries_with_data.sort(key=lambda x: (0 if x['is_unread'] else 1, x['position'])) + + return render_template('create_inquiry.html', recent_inquiries=inquiries_with_data) @app.route('/', methods=['POST']) @limiter.limit("5 per hour") @@ -40,7 +84,8 @@ def create_inquiry(): webhooks.inquiry_created(new_inquiry.id, initial_message) except WebhookError as e: app.logger.error(f"Error sending webhook for new inquiry: {str(e)}") - + + # Add to recent inquiries cookie return redirect(url_for('inquiry', inquiry_id=new_inquiry.id)) @app.route('/inquiry/', methods=['GET', 'POST']) @@ -48,7 +93,7 @@ def create_inquiry(): def inquiry(inquiry_id): inquiry = Inquiry.query.get_or_404(inquiry_id) is_admin_user = is_admin() - + if request.method == 'POST': # Handle closed inquiry differently for admin vs non-admin if inquiry.is_closed: @@ -81,10 +126,57 @@ def inquiry(inquiry_id): except WebhookError as e: app.logger.error(f"Error sending webhook for message: {str(e)}") - return redirect(url_for('inquiry', inquiry_id=inquiry_id)) + response = redirect(url_for('inquiry', inquiry_id=inquiry_id)) + return add_inquiry_to_cookie(response, inquiry_id) if not is_admin_user else response messages = Message.query.filter_by(inquiry_id=inquiry_id).order_by(Message.message_number).all() - return render_template('inquiry.html', inquiry=inquiry, messages=messages) + + # Update the recent inquiries cookie when viewing an inquiry + response = make_response(render_template('inquiry.html', inquiry=inquiry, messages=messages)) + if not is_admin_user: # Only track inquiries for regular users, not admins + response = add_inquiry_to_cookie(response, inquiry_id) + + # Mark all messages in this inquiry as read + if messages: + last_message_number = messages[-1].message_number + read_status = {} + + if request.cookies.get('read_messages'): + try: + read_status = json.loads(request.cookies.get('read_messages')) + except: + pass + + # Store directly as inquiry_id: last_read_message_number mapping + read_status[inquiry_id] = last_message_number + expires = datetime.now(timezone.utc) + timedelta(days=30) + response.set_cookie('read_messages', json.dumps(read_status), expires=expires, httponly=True) + + return response + +# Helper function to add an inquiry to the recent inquiries cookie +def add_inquiry_to_cookie(response, inquiry_id): + recent_inquiries = [] + if request.cookies.get('recent_inquiries'): + try: + recent_inquiries = json.loads(request.cookies.get('recent_inquiries')) + except: + recent_inquiries = [] + + # Add the current inquiry to the front of the list if it's not already there + if inquiry_id in recent_inquiries: + recent_inquiries.remove(inquiry_id) + recent_inquiries.insert(0, inquiry_id) + + # Keep only the 5 most recent inquiries + recent_inquiries = recent_inquiries[:5] + + # Set the cookie to expire in 30 days + expires = datetime.now(timezone.utc) + timedelta(days=30) + + # Set the cookie + response.set_cookie('recent_inquiries', json.dumps(recent_inquiries), expires=expires, httponly=True) + return response @app.route('/inquiry//delete', methods=['POST']) @limiter.limit("10 per hour", deduct_when=lambda response: response.status_code != 200) @@ -181,4 +273,18 @@ def reopen_inquiry(inquiry_id): if referrer and 'admin/dashboard' in referrer: return redirect(url_for('admin_dashboard')) else: - return redirect(url_for('inquiry', inquiry_id=inquiry_id)) \ No newline at end of file + return redirect(url_for('inquiry', inquiry_id=inquiry_id)) + +@app.route('/forget_inquiries', methods=['POST']) +def forget_inquiries(): + # Create a response that redirects to the index page + response = redirect(url_for('index')) + + # Clear the recent inquiries cookie by setting it to empty and expiring immediately + response.set_cookie('recent_inquiries', '', expires=0) + + # Also clear the read messages status cookie + response.set_cookie('read_messages', '', expires=0) + + flash('Your recent conversations have been forgotten.') + return response \ No newline at end of file diff --git a/src/anonchat/static/css/styles.css b/src/anonchat/static/css/styles.css index 84d2771..9aa8acb 100644 --- a/src/anonchat/static/css/styles.css +++ b/src/anonchat/static/css/styles.css @@ -142,4 +142,140 @@ th { .btn-primary:hover { background-color: #2d6c30; text-decoration: none; -} \ No newline at end of file +} + +/* Recent Inquiries Styles */ +.recent-inquiries { + margin-top: 2rem; + padding-top: 1.5rem; + border-top: 1px solid #444; +} + +.recent-header { + display: flex; + align-items: center; + margin-bottom: 1rem; +} + +.recent-header h3 { + margin: 0; + color: #bbb; +} + +.recent-header form { + margin: 0; +} + +.forget-button { + background-color: #8c3b3b; + color: white; + padding: 0.4rem 0.8rem; + font-size: 0.9rem; + margin-left: 1rem; +} + +.forget-button:hover { + background-color: #a04343; +} + +.inquiry-list { + list-style: none; + padding: 0; + margin: 0; +} + +.inquiry-list li { + margin-bottom: 0.8rem; +} + +.inquiry-list a { + text-decoration: none; +} + +.inquiry-list a:hover { + text-decoration: none; +} + +.inquiry-item { + padding: 1rem; + background-color: #333; + border-radius: 4px; + transition: background-color 0.2s ease; + border-left: 3px solid transparent; +} + +.inquiry-item:hover { + background-color: #3a3a3a; +} + +/* Unread inquiry styling */ +.inquiry-item.unread { + border-left: 3px solid #3b8c3e; + background-color: #383f38; +} + +.inquiry-item.unread:hover { + background-color: #404a40; +} + +.inquiry-header { + display: flex; + align-items: center; + margin-bottom: 0.5rem; +} + +.inquiry-id { + font-weight: bold; + display: inline-block; + margin-right: 0.5rem; +} + +.inquiry-status { + font-size: 0.8rem; + padding: 2px 6px; + border-radius: 3px; + display: inline-block; +} + +.inquiry-status.open { + background-color: #3b8c3e; + color: white; +} + +.inquiry-status.closed { + background-color: #8c3b3b; + color: white; +} + +.unread-badge { + margin-left: auto; + background-color: #3b8c3e; + color: white; + padding: 2px 8px; + border-radius: 10px; + font-size: 0.75rem; + font-weight: bold; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { + box-shadow: 0 0 0 0 rgba(59, 140, 62, 0.4); + } + 70% { + box-shadow: 0 0 0 6px rgba(59, 140, 62, 0); + } + 100% { + box-shadow: 0 0 0 0 rgba(59, 140, 62, 0); + } +} + +.inquiry-message { + margin: 0.4rem 0; + color: #bbb; + font-size: 0.9rem; +} + +h2, h3 { + padding-left: 1rem; +} \ No newline at end of file diff --git a/src/anonchat/templates/create_inquiry.html b/src/anonchat/templates/create_inquiry.html index 9b761c1..5977eac 100644 --- a/src/anonchat/templates/create_inquiry.html +++ b/src/anonchat/templates/create_inquiry.html @@ -19,4 +19,41 @@ + + {% if recent_inquiries %} + + {% endif %} {% endblock %} \ No newline at end of file diff --git a/src/anonchat/templates/inquiry.html b/src/anonchat/templates/inquiry.html index c86e738..36cbfec 100644 --- a/src/anonchat/templates/inquiry.html +++ b/src/anonchat/templates/inquiry.html @@ -1,10 +1,10 @@ {% extends "base.html" %} -{% block title %}Inquiry #{{ inquiry.id[:6] }}{% endblock %} +{% block title %}Inquiry #{{ inquiry.id[:8] }}{% endblock %} {% block header %}

- Inquiry #{{ inquiry.id[:6] }} + Inquiry #{{ inquiry.id[:8] }} {% if inquiry.is_closed %} CLOSED