Features Added: 🔥 Modern Chat System: - Real-time messaging with modern Tailwind CSS design - Post-linked discussions for adventure sharing - Chat categories (general, technical-support, adventure-planning) - Mobile-responsive interface with gradient backgrounds - JavaScript polling for live message updates 🎯 Comprehensive Admin Panel: - Chat room management with merge capabilities - Password reset system with email templates - User management with admin controls - Chat statistics and analytics dashboard - Room binding to posts and categorization �� Mobile API Integration: - RESTful API endpoints at /api/v1/chat - Session-based authentication for mobile apps - Comprehensive endpoints for rooms, messages, users - Mobile app compatibility (React Native, Flutter) 🛠️ Technical Improvements: - Enhanced database models with ChatRoom categories - Password reset token system with email verification - Template synchronization fixes for Docker deployment - Migration scripts for database schema updates - Improved error handling and validation 🎨 UI/UX Enhancements: - Modern card-based layouts matching app design - Consistent styling across chat and admin interfaces - Mobile-optimized touch interactions - Professional gradient designs and glass morphism effects 📚 Documentation: - Updated README with comprehensive API documentation - Added deployment instructions for Docker (port 8100) - Configuration guide for production environments - Mobile integration examples and endpoints This update transforms the platform into a comprehensive motorcycle adventure community with modern chat capabilities and professional admin management tools.
243 lines
11 KiB
HTML
243 lines
11 KiB
HTML
{% extends "admin/base.html" %}
|
|
|
|
{% block title %}Password Reset Request #{{ reset_request.id }} - Admin{% endblock %}
|
|
|
|
{% block admin_content %}
|
|
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
|
|
<h1 class="h2">Password Reset Request #{{ reset_request.id }}</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<a href="{{ url_for('admin.password_reset_requests') }}" class="btn btn-outline-secondary btn-sm me-2">
|
|
<i class="fas fa-arrow-left"></i> Back to List
|
|
</a>
|
|
{% if reset_request.user and reset_request.status == 'pending' %}
|
|
<form method="POST" action="{{ url_for('admin.generate_password_reset_token', request_id=reset_request.id) }}"
|
|
class="d-inline" onsubmit="return confirm('Generate reset token for {{ reset_request.user_email }}?')">
|
|
<button type="submit" class="btn btn-success btn-sm">
|
|
<i class="fas fa-key"></i> Generate Reset Token
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<!-- Request Information -->
|
|
<div class="col-lg-8">
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="m-0 fw-bold text-primary">Request Details</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">Status:</div>
|
|
<div class="col-sm-9">
|
|
{% if reset_request.status == 'pending' %}
|
|
<span class="badge bg-warning fs-6">
|
|
<i class="fas fa-clock"></i> Pending
|
|
</span>
|
|
{% elif reset_request.status == 'token_generated' %}
|
|
<span class="badge bg-info fs-6">
|
|
<i class="fas fa-link"></i> Token Generated
|
|
</span>
|
|
{% elif reset_request.status == 'completed' %}
|
|
<span class="badge bg-success fs-6">
|
|
<i class="fas fa-check-circle"></i> Completed
|
|
</span>
|
|
{% elif reset_request.status == 'expired' %}
|
|
<span class="badge bg-secondary fs-6">
|
|
<i class="fas fa-calendar-times"></i> Expired
|
|
</span>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">User Email:</div>
|
|
<div class="col-sm-9">
|
|
<span class="fw-bold">{{ reset_request.user_email }}</span>
|
|
{% if reset_request.user %}
|
|
<br><small class="text-success">
|
|
<i class="fas fa-user-check"></i> User found: {{ reset_request.user.nickname }}
|
|
</small>
|
|
{% else %}
|
|
<br><small class="text-danger">
|
|
<i class="fas fa-user-times"></i> User not found in system
|
|
</small>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">Requested:</div>
|
|
<div class="col-sm-9">
|
|
{{ reset_request.created_at.strftime('%B %d, %Y at %I:%M %p') }}
|
|
<br><small class="text-muted">{{ reset_request.created_at.strftime('%A') }}</small>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">Last Updated:</div>
|
|
<div class="col-sm-9">
|
|
{{ reset_request.updated_at.strftime('%B %d, %Y at %I:%M %p') }}
|
|
</div>
|
|
</div>
|
|
|
|
{% if reset_request.requester_message %}
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">Original Message:</div>
|
|
<div class="col-sm-9">
|
|
<div class="bg-light p-3 rounded">
|
|
{{ reset_request.requester_message }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% if reset_request.chat_message %}
|
|
<div class="row mb-3">
|
|
<div class="col-sm-3 fw-bold">Chat Reference:</div>
|
|
<div class="col-sm-9">
|
|
<a href="{{ url_for('chat.room', room_id=reset_request.chat_message.room_id) }}"
|
|
class="btn btn-outline-info btn-sm">
|
|
<i class="fas fa-comments"></i> View in Chat
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Admin Notes -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="m-0 fw-bold text-primary">Admin Notes</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<form method="POST" action="{{ url_for('admin.update_password_reset_notes', request_id=reset_request.id) }}">
|
|
<div class="mb-3">
|
|
<label for="admin_notes" class="form-label">Notes (visible only to admins):</label>
|
|
<textarea class="form-control" id="admin_notes" name="admin_notes" rows="4"
|
|
placeholder="Add notes about this password reset request...">{{ reset_request.admin_notes or '' }}</textarea>
|
|
</div>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-save"></i> Save Notes
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sidebar -->
|
|
<div class="col-lg-4">
|
|
<!-- Generated Tokens -->
|
|
<div class="card mb-4">
|
|
<div class="card-header">
|
|
<h6 class="m-0 fw-bold text-primary">Generated Tokens ({{ reset_request.tokens|length }})</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if reset_request.tokens %}
|
|
{% for token in reset_request.tokens %}
|
|
<div class="border rounded p-3 mb-3 {{ 'bg-light' if not token.is_valid else '' }}">
|
|
<div class="d-flex justify-content-between align-items-start mb-2">
|
|
<div>
|
|
{% if token.is_used %}
|
|
<span class="badge bg-success">Used</span>
|
|
{% elif token.is_expired %}
|
|
<span class="badge bg-secondary">Expired</span>
|
|
{% else %}
|
|
<span class="badge bg-warning">Active</span>
|
|
{% endif %}
|
|
</div>
|
|
<small class="text-muted">
|
|
{{ token.created_at.strftime('%m/%d %H:%M') }}
|
|
</small>
|
|
</div>
|
|
|
|
<div class="small mb-2">
|
|
<strong>Token:</strong> {{ token.token[:12] }}...
|
|
</div>
|
|
|
|
<div class="small mb-2">
|
|
<strong>Expires:</strong> {{ token.expires_at.strftime('%m/%d/%Y %H:%M') }}
|
|
</div>
|
|
|
|
{% if token.is_used %}
|
|
<div class="small mb-2">
|
|
<strong>Used:</strong> {{ token.used_at.strftime('%m/%d/%Y %H:%M') }}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="small mb-2">
|
|
<strong>Created by:</strong> {{ token.created_by_admin.nickname }}
|
|
</div>
|
|
|
|
{% if token.is_valid %}
|
|
<div class="mt-2">
|
|
<a href="{{ url_for('admin.password_reset_token_template', token_id=token.id) }}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-envelope"></i> Email Template
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
{% endfor %}
|
|
{% else %}
|
|
<div class="text-center text-muted py-4">
|
|
<i class="fas fa-key fa-2x mb-2"></i>
|
|
<p>No tokens generated yet.</p>
|
|
{% if reset_request.user and reset_request.status == 'pending' %}
|
|
<form method="POST" action="{{ url_for('admin.generate_password_reset_token', request_id=reset_request.id) }}"
|
|
onsubmit="return confirm('Generate reset token for {{ reset_request.user_email }}?')">
|
|
<button type="submit" class="btn btn-success btn-sm">
|
|
<i class="fas fa-key"></i> Generate Token
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Information -->
|
|
{% if reset_request.user %}
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="m-0 fw-bold text-primary">User Information</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-2">
|
|
<strong>Username:</strong> {{ reset_request.user.nickname }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Email:</strong> {{ reset_request.user.email }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Account Created:</strong> {{ reset_request.user.created_at.strftime('%m/%d/%Y') }}
|
|
</div>
|
|
<div class="mb-2">
|
|
<strong>Admin:</strong>
|
|
{% if reset_request.user.is_admin %}
|
|
<span class="badge bg-danger">Yes</span>
|
|
{% else %}
|
|
<span class="badge bg-secondary">No</span>
|
|
{% endif %}
|
|
</div>
|
|
<div class="mb-3">
|
|
<strong>Active:</strong>
|
|
{% if reset_request.user.is_active %}
|
|
<span class="badge bg-success">Yes</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">No</span>
|
|
{% endif %}
|
|
</div>
|
|
<a href="{{ url_for('admin.user_detail', user_id=reset_request.user.id) }}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-user"></i> View User Profile
|
|
</a>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|