Files
moto-adv-website/app/templates/community/profile.html
ske087 1661f5f588 feat: Complete chat system implementation and password reset enhancement
- Add comprehensive chat system with modern UI design
- Implement admin-based password reset system
- Fix template syntax errors and 500 server errors
- Add chat routes, API endpoints, and database models
- Enhance user interface with Tailwind CSS card-based design
- Implement community guidelines and quick action features
- Add responsive design for mobile and desktop compatibility
- Create support chat functionality with admin integration
- Fix JavaScript inheritance in base template
- Add database migration for chat system tables

Features:
 Modern chat interface with room management
 Admin-based password reset workflow
 Real-time chat with mobile app support
 Professional UI with gradient cards and hover effects
 Community guidelines and safety features
 Responsive design for all devices
 Error-free template rendering
2025-08-09 20:44:25 +03:00

445 lines
22 KiB
HTML

{% extends "base.html" %}
{% block title %}My Profile - {{ current_user.nickname }}{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 pt-16">
<!-- Profile Header -->
<div class="relative overflow-hidden py-16">
<div class="absolute inset-0 bg-black/20"></div>
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-8 border border-white/20">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div class="flex items-center space-x-6">
<div class="w-20 h-20 bg-gradient-to-r from-orange-500 to-red-600 rounded-full flex items-center justify-center text-white font-bold text-3xl">
{{ current_user.nickname[0].upper() }}
</div>
<div>
<h1 class="text-3xl font-bold text-white mb-2">{{ current_user.nickname }}</h1>
<p class="text-blue-200 mb-1">
<i class="fas fa-envelope mr-2"></i>{{ current_user.email }}
</p>
<p class="text-blue-200">
<i class="fas fa-calendar mr-2"></i>Riding since {{ current_user.created_at.strftime('%B %Y') }}
</p>
</div>
</div>
<div>
<a href="{{ url_for('community.new_post') }}"
class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-green-600 to-emerald-600 text-white font-semibold rounded-lg hover:from-green-700 hover:to-emerald-700 transition-all duration-200 transform hover:scale-105">
<i class="fas fa-plus mr-2"></i>Share New Adventure
</a>
</div>
</div>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12 -mt-8">
<!-- Adventure Statistics -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-green-600 to-emerald-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-check-circle text-3xl mr-4"></i>
<div>
<div class="text-3xl font-bold">{{ published_count }}</div>
<div class="text-green-100">Published Adventures</div>
</div>
</div>
</div>
<div class="p-4">
<p class="text-gray-600 text-sm">Visible to the community</p>
</div>
</div>
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-yellow-600 to-orange-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-clock text-3xl mr-4"></i>
<div>
<div class="text-3xl font-bold">{{ pending_count }}</div>
<div class="text-yellow-100">Awaiting Review</div>
</div>
</div>
</div>
<div class="p-4">
<p class="text-gray-600 text-sm">Pending admin approval</p>
</div>
</div>
</div>
<!-- Change Password Card -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden mb-8">
<div class="cursor-pointer transition-all duration-200 hover:bg-gray-50" onclick="togglePasswordCard()">
<div class="p-6">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-key text-3xl text-purple-600 mr-4"></i>
<div>
<h3 class="text-xl font-bold text-gray-900">Change Password</h3>
<p class="text-gray-600">Update your account password for security</p>
</div>
</div>
<i id="passwordCardToggle" class="fas fa-chevron-down text-gray-400 text-xl transition-transform duration-200"></i>
</div>
</div>
</div>
<!-- Password Change Form (Initially Hidden) -->
<div id="passwordChangeForm" class="hidden border-t border-gray-200">
<div class="p-6 bg-gray-50">
<form id="changePasswordForm" method="POST" action="{{ url_for('auth.change_password') }}" class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<!-- Current Password -->
<div>
<label for="current_password" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-lock mr-1"></i>Current Password
</label>
<input type="password"
id="current_password"
name="current_password"
required
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
placeholder="Enter current password">
</div>
<!-- New Password -->
<div>
<label for="new_password" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-key mr-1"></i>New Password
</label>
<input type="password"
id="new_password"
name="new_password"
required
minlength="6"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
placeholder="Enter new password">
</div>
<!-- Confirm New Password -->
<div>
<label for="confirm_password" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-check-circle mr-1"></i>Confirm Password
</label>
<input type="password"
id="confirm_password"
name="confirm_password"
required
minlength="6"
class="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-purple-500 focus:border-transparent transition-all duration-200"
placeholder="Confirm new password">
</div>
</div>
<!-- Password Requirements -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<h4 class="font-semibold text-blue-800 mb-2">Password Requirements:</h4>
<ul class="text-sm text-blue-700 space-y-1">
<li><i class="fas fa-check text-green-500 mr-1"></i>At least 6 characters long</li>
<li><i class="fas fa-info-circle text-blue-500 mr-1"></i>Use a unique password you don't use elsewhere</li>
<li><i class="fas fa-shield-alt text-green-500 mr-1"></i>Consider using a mix of letters, numbers, and symbols</li>
</ul>
</div>
<!-- Submit Button -->
<div class="flex justify-end">
<button type="submit"
class="inline-flex items-center px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-semibold rounded-lg hover:from-purple-700 hover:to-blue-700 transition-all duration-200 transform hover:scale-105">
<i class="fas fa-save mr-2"></i>Update Password
</button>
</div>
</form>
</div>
</div>
</div>
<!-- Adventures Collection Header -->
<div class="bg-white rounded-2xl shadow-xl p-6 mb-8">
<div class="flex items-center">
<i class="fas fa-mountain text-3xl text-blue-600 mr-4"></i>
<div>
<h2 class="text-2xl font-bold text-gray-900">My Adventure Collection</h2>
<p class="text-gray-600">Manage and share your motorcycle adventures</p>
</div>
</div>
</div>
<!-- Adventures Grid -->
{% if posts.items %}
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{% for post in posts.items %}
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl hover:scale-105">
<!-- Adventure Image -->
{% set cover_image = post.images | selectattr('is_cover', 'equalto', True) | first %}
{% if cover_image %}
<div class="relative">
<img src="{{ cover_image.get_thumbnail_url() }}"
alt="{{ post.title }}"
class="w-full h-48 object-cover">
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent"></div>
<div class="absolute top-3 right-3">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold
{{ 'bg-green-500 text-white' if post.published else 'bg-yellow-500 text-black' }}">
{% if post.published %}
<i class="fas fa-check-circle mr-1"></i>Live
{% else %}
<i class="fas fa-clock mr-1"></i>Review
{% endif %}
</span>
</div>
</div>
{% else %}
<div class="h-48 bg-gradient-to-br from-blue-600 to-purple-600 flex items-center justify-center relative">
<i class="fas fa-mountain text-white text-6xl opacity-50"></i>
<div class="absolute top-3 right-3">
<span class="inline-flex items-center px-2 py-1 rounded-full text-xs font-semibold
{{ 'bg-green-500 text-white' if post.published else 'bg-yellow-500 text-black' }}">
{% if post.published %}
<i class="fas fa-check-circle mr-1"></i>Live
{% else %}
<i class="fas fa-clock mr-1"></i>Review
{% endif %}
</span>
</div>
</div>
{% endif %}
<div class="p-6">
<!-- Title and Subtitle -->
<h3 class="text-xl font-bold text-gray-900 mb-2">{{ post.title }}</h3>
{% if post.subtitle %}
<p class="text-gray-600 text-sm mb-3">{{ post.subtitle }}</p>
{% endif %}
<!-- Difficulty Badge -->
<div class="mb-4">
<span class="inline-flex items-center px-3 py-1 rounded-full bg-gradient-to-r from-yellow-500 to-orange-500 text-white text-sm font-semibold">
{% for i in range(post.difficulty) %}
<i class="fas fa-star mr-1"></i>
{% endfor %}
{{ post.get_difficulty_label() }}
</span>
</div>
<!-- Content Preview -->
<p class="text-gray-600 text-sm mb-4 h-12 overflow-hidden">
{{ (post.content[:100] + '...') if post.content|length > 100 else post.content }}
</p>
<!-- Adventure Metadata -->
<div class="bg-gray-50 rounded-lg p-4 mb-4">
<div class="grid grid-cols-3 gap-4 text-center">
<div>
<i class="fas fa-calendar text-blue-500 mb-1"></i>
<div class="text-sm font-semibold">{{ post.created_at.strftime('%m/%d') }}</div>
<div class="text-xs text-gray-500">Created</div>
</div>
<div>
<i class="fas fa-images text-green-500 mb-1"></i>
<div class="text-sm font-semibold">{{ post.images.count() }}</div>
<div class="text-xs text-gray-500">Photos</div>
</div>
<div>
<i class="fas fa-route text-orange-500 mb-1"></i>
<div class="text-sm font-semibold">{{ post.gpx_files.count() }}</div>
<div class="text-xs text-gray-500">Routes</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="space-y-2">
{% if post.published %}
<a href="{{ url_for('community.post_detail', id=post.id) }}"
class="block w-full text-center px-4 py-2 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-all duration-200">
<i class="fas fa-eye mr-2"></i>View Live
</a>
{% endif %}
<div class="grid grid-cols-2 gap-2">
<a href="{{ url_for('community.edit_post', id=post.id) }}"
class="text-center px-3 py-2 bg-yellow-500 text-white font-semibold rounded-lg hover:bg-yellow-600 transition-all duration-200">
<i class="fas fa-edit mr-1"></i>Edit
</a>
<button type="button"
class="px-3 py-2 bg-red-500 text-white font-semibold rounded-lg hover:bg-red-600 transition-all duration-200"
data-post-id="{{ post.id }}"
data-post-title="{{ post.title }}"
onclick="confirmDelete(this)">
<i class="fas fa-trash mr-1"></i>Delete
</button>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<!-- No Adventures State -->
<div class="bg-white rounded-2xl shadow-xl p-12 text-center">
<i class="fas fa-mountain text-6xl text-gray-400 mb-6"></i>
<h3 class="text-2xl font-bold text-gray-900 mb-4">Your Adventure Journey Awaits!</h3>
<p class="text-gray-600 mb-8 max-w-md mx-auto">
You haven't shared any motorcycle adventures yet. Every great ride has a story worth telling!
</p>
<a href="{{ url_for('community.new_post') }}"
class="inline-flex items-center px-8 py-4 bg-gradient-to-r from-green-600 to-emerald-600 text-white font-semibold rounded-lg hover:from-green-700 hover:to-emerald-700 transition-all duration-200 transform hover:scale-105">
<i class="fas fa-plus mr-2"></i>Share Your First Adventure
</a>
<div class="mt-6">
<p class="text-sm text-gray-500">
<i class="fas fa-lightbulb mr-1"></i>
Share photos, GPS tracks, and stories of your rides to inspire the community
</p>
</div>
</div>
{% endif %}
<!-- Pagination -->
{% if posts.pages > 1 %}
<div class="flex justify-center mt-8">
<div class="bg-white rounded-lg shadow-lg px-6 py-4">
<div class="flex items-center space-x-4">
{% if posts.has_prev %}
<a href="{{ url_for('community.profile', page=posts.prev_num) }}"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200">
<i class="fas fa-chevron-left mr-1"></i>Previous
</a>
{% endif %}
<span class="text-gray-600">
Page {{ posts.page }} of {{ posts.pages }}
</span>
{% if posts.has_next %}
<a href="{{ url_for('community.profile', page=posts.next_num) }}"
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-all duration-200">
Next<i class="fas fa-chevron-right ml-1"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Delete Confirmation Modal -->
<div id="deleteModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden">
<div class="bg-white rounded-2xl p-8 max-w-md mx-4">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-red-500 text-4xl mb-4"></i>
<h3 class="text-xl font-bold text-gray-900 mb-4">Delete Adventure</h3>
<p class="text-gray-600 mb-6">
Are you sure you want to delete "<span id="deletePostTitle" class="font-semibold"></span>"?
This action cannot be undone.
</p>
<div class="flex space-x-4">
<button onclick="closeDeleteModal()"
class="flex-1 px-4 py-2 bg-gray-300 text-gray-700 rounded-lg hover:bg-gray-400 transition-all duration-200">
Cancel
</button>
<form id="deleteForm" method="POST" class="flex-1">
<button type="submit"
class="w-full px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-all duration-200">
Delete
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function confirmDelete(button) {
const postId = button.getAttribute('data-post-id');
const postTitle = button.getAttribute('data-post-title');
document.getElementById('deletePostTitle').textContent = postTitle;
document.getElementById('deleteForm').action = `/community/delete-post/${postId}`;
document.getElementById('deleteModal').classList.remove('hidden');
}
function closeDeleteModal() {
document.getElementById('deleteModal').classList.add('hidden');
}
// Password card toggle functionality
function togglePasswordCard() {
const form = document.getElementById('passwordChangeForm');
const toggle = document.getElementById('passwordCardToggle');
if (form && toggle) {
if (form.classList.contains('hidden')) {
form.classList.remove('hidden');
toggle.classList.remove('fa-chevron-down');
toggle.classList.add('fa-chevron-up');
} else {
form.classList.add('hidden');
toggle.classList.remove('fa-chevron-up');
toggle.classList.add('fa-chevron-down');
}
}
}
// Make sure DOM is loaded before attaching event listeners
document.addEventListener('DOMContentLoaded', function() {
// Ensure the toggle function is available globally
window.togglePasswordCard = togglePasswordCard;
});
// Password change form validation
document.getElementById('changePasswordForm').addEventListener('submit', function(e) {
const newPassword = document.getElementById('new_password').value;
const confirmPassword = document.getElementById('confirm_password').value;
if (newPassword !== confirmPassword) {
e.preventDefault();
alert('New password and confirm password do not match. Please try again.');
document.getElementById('confirm_password').focus();
return false;
}
if (newPassword.length < 6) {
e.preventDefault();
alert('Password must be at least 6 characters long.');
document.getElementById('new_password').focus();
return false;
}
});
// Real-time password confirmation validation
document.getElementById('confirm_password').addEventListener('input', function() {
const newPassword = document.getElementById('new_password').value;
const confirmPassword = this.value;
if (confirmPassword && newPassword !== confirmPassword) {
this.style.borderColor = '#ef4444';
this.style.backgroundColor = '#fef2f2';
} else if (confirmPassword && newPassword === confirmPassword) {
this.style.borderColor = '#10b981';
this.style.backgroundColor = '#f0fdf4';
} else {
this.style.borderColor = '#d1d5db';
this.style.backgroundColor = '#ffffff';
}
});
// Close modal when clicking outside
document.getElementById('deleteModal').addEventListener('click', function(e) {
if (e.target === this) {
closeDeleteModal();
}
});
// Close modal on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeDeleteModal();
}
});
</script>
{% endblock %}