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.
231 lines
10 KiB
HTML
231 lines
10 KiB
HTML
{% extends "admin/base.html" %}
|
|
|
|
{% block title %}Password Reset Requests - 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 Requests</h1>
|
|
<div class="btn-toolbar mb-2 mb-md-0">
|
|
<div class="btn-group me-2">
|
|
<a href="{{ url_for('admin.password_reset_requests', status='all') }}"
|
|
class="btn btn-sm {{ 'btn-primary' if status == 'all' else 'btn-outline-secondary' }}">
|
|
All Requests
|
|
</a>
|
|
<a href="{{ url_for('admin.password_reset_requests', status='pending') }}"
|
|
class="btn btn-sm {{ 'btn-warning' if status == 'pending' else 'btn-outline-secondary' }}">
|
|
Pending
|
|
</a>
|
|
<a href="{{ url_for('admin.password_reset_requests', status='token_generated') }}"
|
|
class="btn btn-sm {{ 'btn-info' if status == 'token_generated' else 'btn-outline-secondary' }}">
|
|
Token Generated
|
|
</a>
|
|
<a href="{{ url_for('admin.password_reset_requests', status='completed') }}"
|
|
class="btn btn-sm {{ 'btn-success' if status == 'completed' else 'btn-outline-secondary' }}">
|
|
Completed
|
|
</a>
|
|
</div>
|
|
<button type="button" class="btn btn-sm btn-outline-secondary" onclick="location.reload()">
|
|
<i class="fas fa-sync-alt"></i> Refresh
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{% if requests.items %}
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="m-0 fw-bold text-primary">
|
|
{{ requests.total }} Password Reset {{ 'Request' if requests.total == 1 else 'Requests' }}
|
|
{% if status != 'all' %}({{ status.replace('_', ' ').title() }}){% endif %}
|
|
</h6>
|
|
</div>
|
|
<div class="card-body p-0">
|
|
<div class="table-responsive">
|
|
<table class="table table-striped table-hover mb-0">
|
|
<thead class="table-dark">
|
|
<tr>
|
|
<th>Request Date</th>
|
|
<th>User Email</th>
|
|
<th>User Found</th>
|
|
<th>Status</th>
|
|
<th>Generated Tokens</th>
|
|
<th>Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for request in requests.items %}
|
|
<tr>
|
|
<td>
|
|
<div class="fw-bold">{{ request.created_at.strftime('%Y-%m-%d') }}</div>
|
|
<small class="text-muted">{{ request.created_at.strftime('%H:%M:%S') }}</small>
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ request.user_email }}</div>
|
|
{% if request.user %}
|
|
<small class="text-success">
|
|
<i class="fas fa-user-check"></i> {{ request.user.nickname }}
|
|
</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if request.user %}
|
|
<span class="badge bg-success">
|
|
<i class="fas fa-check"></i> Found
|
|
</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">
|
|
<i class="fas fa-times"></i> Not Found
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
{% if request.status == 'pending' %}
|
|
<span class="badge bg-warning">
|
|
<i class="fas fa-clock"></i> Pending
|
|
</span>
|
|
{% elif request.status == 'token_generated' %}
|
|
<span class="badge bg-info">
|
|
<i class="fas fa-link"></i> Token Generated
|
|
</span>
|
|
{% elif request.status == 'completed' %}
|
|
<span class="badge bg-success">
|
|
<i class="fas fa-check-circle"></i> Completed
|
|
</span>
|
|
{% elif request.status == 'expired' %}
|
|
<span class="badge bg-secondary">
|
|
<i class="fas fa-calendar-times"></i> Expired
|
|
</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="fw-bold">{{ request.tokens|length }}</div>
|
|
{% set active_tokens = request.tokens|selectattr('is_valid')|list %}
|
|
{% if active_tokens %}
|
|
<small class="text-success">{{ active_tokens|length }} active</small>
|
|
{% else %}
|
|
<small class="text-muted">None active</small>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="{{ url_for('admin.password_reset_request_detail', request_id=request.id) }}"
|
|
class="btn btn-outline-primary" title="View Details">
|
|
<i class="fas fa-eye"></i>
|
|
</a>
|
|
{% if request.user and request.status == 'pending' %}
|
|
<form method="POST" action="{{ url_for('admin.generate_password_reset_token', request_id=request.id) }}"
|
|
class="d-inline" onsubmit="return confirm('Generate reset token for {{ request.user_email }}?')">
|
|
<button type="submit" class="btn btn-outline-success" title="Generate Token">
|
|
<i class="fas fa-key"></i>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if requests.pages > 1 %}
|
|
<nav aria-label="Page navigation" class="mt-3">
|
|
<ul class="pagination justify-content-center">
|
|
{% if requests.has_prev %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('admin.password_reset_requests', page=requests.prev_num, status=status) }}">
|
|
Previous
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for page_num in requests.iter_pages() %}
|
|
{% if page_num %}
|
|
{% if page_num != requests.page %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('admin.password_reset_requests', page=page_num, status=status) }}">
|
|
{{ page_num }}
|
|
</a>
|
|
</li>
|
|
{% else %}
|
|
<li class="page-item active">
|
|
<span class="page-link">{{ page_num }}</span>
|
|
</li>
|
|
{% endif %}
|
|
{% else %}
|
|
<li class="page-item disabled">
|
|
<span class="page-link">…</span>
|
|
</li>
|
|
{% endif %}
|
|
{% endfor %}
|
|
|
|
{% if requests.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('admin.password_reset_requests', page=requests.next_num, status=status) }}">
|
|
Next
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<div class="card">
|
|
<div class="card-body text-center py-5">
|
|
<i class="fas fa-key fa-3x text-muted mb-3"></i>
|
|
<h5 class="text-muted">No Password Reset Requests</h5>
|
|
<p class="text-muted">
|
|
{% if status == 'all' %}
|
|
No password reset requests have been made yet.
|
|
{% else %}
|
|
No {{ status.replace('_', ' ') }} password reset requests found.
|
|
{% endif %}
|
|
</p>
|
|
{% if status != 'all' %}
|
|
<a href="{{ url_for('admin.password_reset_requests', status='all') }}" class="btn btn-primary">
|
|
View All Requests
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Help Information -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card border-info">
|
|
<div class="card-header bg-info text-white">
|
|
<h6 class="m-0"><i class="fas fa-info-circle"></i> How Password Reset Works</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="row">
|
|
<div class="col-md-6">
|
|
<h6 class="fw-bold">Process Flow:</h6>
|
|
<ol class="small">
|
|
<li>User requests password reset through chat system</li>
|
|
<li>Request appears here with "Pending" status</li>
|
|
<li>Admin generates one-time reset token (24h expiry)</li>
|
|
<li>Admin copies email template and sends to user</li>
|
|
<li>User clicks link and resets password</li>
|
|
<li>Token becomes "Used" and request "Completed"</li>
|
|
</ol>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<h6 class="fw-bold">Status Meanings:</h6>
|
|
<ul class="small">
|
|
<li><span class="badge bg-warning">Pending</span> - Awaiting admin action</li>
|
|
<li><span class="badge bg-info">Token Generated</span> - Reset link created</li>
|
|
<li><span class="badge bg-success">Completed</span> - Password successfully reset</li>
|
|
<li><span class="badge bg-secondary">Expired</span> - Token expired unused</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|