🎨 Complete Tailwind CSS conversion - Redesigned post detail page with modern gradient backgrounds - Updated profile page with consistent design language - Converted from Bootstrap to Tailwind CSS throughout ✨ New Features & Improvements - Enhanced community post management system - Added admin panel with analytics dashboard - Improved post creation and editing workflows - Interactive GPS map integration with Leaflet.js - Photo gallery with modal view and hover effects - Adventure statistics and metadata display - Like system and community engagement features 🔧 Technical Improvements - Fixed template syntax errors and CSRF token issues - Updated database models and relationships - Enhanced media file management - Improved responsive design patterns - Added proper error handling and validation 📱 Mobile-First Design - Responsive grid layouts - Touch-friendly interactions - Optimized for all screen sizes - Modern card-based UI components 🏍️ Adventure Platform Features - GPS track visualization and statistics - Photo uploads with thumbnail generation - GPX file downloads for registered users - Community comments and discussions - Post approval workflow for admins - Difficulty rating system with star indicators
313 lines
16 KiB
HTML
313 lines
16 KiB
HTML
{% extends "base.html" %}
|
|
{% block title %}My Profile - {{ current_user.nickname }}{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container-fluid mt-4">
|
|
<!-- Header Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<div class="row align-items-center">
|
|
<div class="col-md-8">
|
|
<h1 class="mb-3">
|
|
<i class="fas fa-user-circle text-primary"></i>
|
|
{{ current_user.nickname }}'s Profile
|
|
</h1>
|
|
<p class="text-muted mb-0">
|
|
<i class="fas fa-envelope"></i> {{ current_user.email }}
|
|
• <i class="fas fa-calendar"></i> Member since {{ current_user.created_at.strftime('%B %Y') }}
|
|
</p>
|
|
</div>
|
|
<div class="col-md-4 text-md-end">
|
|
<a href="{{ url_for('community.new_post') }}" class="btn btn-primary btn-lg">
|
|
<i class="fas fa-plus"></i> Create New Adventure
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Section -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card text-center border-success">
|
|
<div class="card-body">
|
|
<i class="fas fa-check-circle fa-3x text-success mb-3"></i>
|
|
<h3 class="text-success">{{ published_count }}</h3>
|
|
<p class="text-muted mb-0">Published Adventures</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-6">
|
|
<div class="card text-center border-warning">
|
|
<div class="card-body">
|
|
<i class="fas fa-clock fa-3x text-warning mb-3"></i>
|
|
<h3 class="text-warning">{{ pending_count }}</h3>
|
|
<p class="text-muted mb-0">Pending Review</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Posts Section -->
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card shadow-sm">
|
|
<div class="card-header bg-dark text-white">
|
|
<h4 class="mb-0">
|
|
<i class="fas fa-list"></i> My Adventures
|
|
</h4>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if posts.items %}
|
|
<div class="row">
|
|
{% for post in posts.items %}
|
|
<div class="col-lg-6 col-xl-4 mb-4">
|
|
<div class="card h-100 {{ 'border-success' if post.published else 'border-warning' }}">
|
|
<!-- Cover Image -->
|
|
{% set cover_image = post.images | selectattr('is_cover', 'equalto', True) | first %}
|
|
{% if cover_image %}
|
|
<div class="position-relative">
|
|
<img src="{{ cover_image.get_thumbnail_url() }}"
|
|
alt="{{ post.title }}"
|
|
class="card-img-top"
|
|
style="height: 200px; object-fit: cover;">
|
|
<div class="position-absolute top-0 end-0 m-2">
|
|
<span class="badge {{ 'bg-success' if post.published else 'bg-warning text-dark' }}">
|
|
{{ 'Published' if post.published else 'Pending Review' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="card-img-top bg-gradient-primary d-flex align-items-center justify-content-center text-white" style="height: 200px;">
|
|
<div class="text-center">
|
|
<i class="fas fa-mountain fa-3x mb-2"></i>
|
|
<div class="position-absolute top-0 end-0 m-2">
|
|
<span class="badge {{ 'bg-success' if post.published else 'bg-warning text-dark' }}">
|
|
{{ 'Published' if post.published else 'Pending Review' }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<div class="card-body d-flex flex-column">
|
|
<!-- Title and Subtitle -->
|
|
<h5 class="card-title">{{ post.title }}</h5>
|
|
{% if post.subtitle %}
|
|
<p class="card-text text-muted small">{{ post.subtitle }}</p>
|
|
{% endif %}
|
|
|
|
<!-- Difficulty -->
|
|
<div class="mb-2">
|
|
<span class="badge bg-warning text-dark">
|
|
{{ '⭐' * post.difficulty }}
|
|
{% if post.difficulty == 1 %}Easy{% elif post.difficulty == 2 %}Moderate{% elif post.difficulty == 3 %}Challenging{% elif post.difficulty == 4 %}Hard{% else %}Expert{% endif %}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Content Preview -->
|
|
<p class="card-text flex-grow-1">
|
|
{{ (post.content[:100] + '...') if post.content|length > 100 else post.content }}
|
|
</p>
|
|
|
|
<!-- Meta Information -->
|
|
<div class="text-muted small mb-3">
|
|
<div>
|
|
<i class="fas fa-calendar"></i>
|
|
Created: {{ post.created_at.strftime('%Y-%m-%d') }}
|
|
</div>
|
|
{% if post.updated_at and post.updated_at != post.created_at %}
|
|
<div>
|
|
<i class="fas fa-edit"></i>
|
|
Updated: {{ post.updated_at.strftime('%Y-%m-%d') }}
|
|
</div>
|
|
{% endif %}
|
|
<div>
|
|
<i class="fas fa-images"></i>
|
|
{{ post.images.count() }} images
|
|
• <i class="fas fa-route"></i>
|
|
{{ post.gpx_files.count() }} GPS tracks
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="d-grid gap-2">
|
|
<div class="btn-group" role="group">
|
|
{% if post.published %}
|
|
<a href="{{ url_for('community.post_detail', id=post.id) }}"
|
|
class="btn btn-outline-primary btn-sm">
|
|
<i class="fas fa-eye"></i> View
|
|
</a>
|
|
{% endif %}
|
|
<a href="{{ url_for('community.edit_post', id=post.id) }}"
|
|
class="btn btn-outline-warning btn-sm">
|
|
<i class="fas fa-edit"></i> Edit
|
|
</a>
|
|
<button type="button" class="btn btn-outline-danger btn-sm"
|
|
onclick="confirmDelete({{ post.id }}, '{{ post.title|replace("'", "\\'") }}')">
|
|
<i class="fas fa-trash"></i> Delete
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Pagination -->
|
|
{% if posts.pages > 1 %}
|
|
<nav aria-label="Posts pagination" class="mt-4">
|
|
<ul class="pagination justify-content-center">
|
|
{% if posts.has_prev %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('community.profile', page=posts.prev_num) }}">
|
|
<i class="fas fa-chevron-left"></i> Previous
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
|
|
{% for page_num in posts.iter_pages() %}
|
|
{% if page_num %}
|
|
{% if page_num != posts.page %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('community.profile', page=page_num) }}">{{ 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 posts.has_next %}
|
|
<li class="page-item">
|
|
<a class="page-link" href="{{ url_for('community.profile', page=posts.next_num) }}">
|
|
Next <i class="fas fa-chevron-right"></i>
|
|
</a>
|
|
</li>
|
|
{% endif %}
|
|
</ul>
|
|
</nav>
|
|
{% endif %}
|
|
|
|
{% else %}
|
|
<!-- No Posts State -->
|
|
<div class="text-center py-5">
|
|
<i class="fas fa-mountain fa-5x text-muted mb-4"></i>
|
|
<h3 class="text-muted">No Adventures Yet</h3>
|
|
<p class="text-muted mb-4">You haven't shared any motorcycle adventures yet. Start your journey!</p>
|
|
<a href="{{ url_for('community.new_post') }}" class="btn btn-primary btn-lg">
|
|
<i class="fas fa-plus"></i> Create Your First Adventure
|
|
</a>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Delete Confirmation Modal -->
|
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
|
<div class="modal-dialog">
|
|
<div class="modal-content">
|
|
<div class="modal-header bg-danger text-white">
|
|
<h5 class="modal-title" id="deleteModalLabel">
|
|
<i class="fas fa-exclamation-triangle"></i> Confirm Deletion
|
|
</h5>
|
|
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
</div>
|
|
<div class="modal-body">
|
|
<p>Are you sure you want to delete the adventure:</p>
|
|
<h6 id="deletePostTitle" class="text-danger"></h6>
|
|
<p class="text-muted small mb-0">
|
|
<i class="fas fa-exclamation-circle"></i>
|
|
This action cannot be undone. All images and GPS tracks will also be deleted.
|
|
</p>
|
|
</div>
|
|
<div class="modal-footer">
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
|
|
<i class="fas fa-times"></i> Cancel
|
|
</button>
|
|
<button type="button" class="btn btn-danger" id="confirmDeleteBtn">
|
|
<i class="fas fa-trash"></i> Yes, Delete Adventure
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let deletePostId = null;
|
|
|
|
function confirmDelete(postId, postTitle) {
|
|
deletePostId = postId;
|
|
document.getElementById('deletePostTitle').textContent = postTitle;
|
|
const modal = new bootstrap.Modal(document.getElementById('deleteModal'));
|
|
modal.show();
|
|
}
|
|
|
|
document.getElementById('confirmDeleteBtn').addEventListener('click', function() {
|
|
if (deletePostId) {
|
|
const btn = this;
|
|
const originalText = btn.innerHTML;
|
|
|
|
// Show loading state
|
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Deleting...';
|
|
btn.disabled = true;
|
|
|
|
// Send delete request
|
|
fetch(`/community/delete-post/${deletePostId}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
// Hide modal
|
|
const modal = bootstrap.Modal.getInstance(document.getElementById('deleteModal'));
|
|
modal.hide();
|
|
|
|
// Show success message and reload page
|
|
const alert = document.createElement('div');
|
|
alert.className = 'alert alert-success alert-dismissible fade show';
|
|
alert.innerHTML = `
|
|
<i class="fas fa-check-circle"></i> ${data.message}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
document.querySelector('.container-fluid').insertBefore(alert, document.querySelector('.container-fluid').firstChild);
|
|
|
|
// Reload page after delay
|
|
setTimeout(() => {
|
|
window.location.reload();
|
|
}, 2000);
|
|
} else {
|
|
throw new Error(data.error || 'Failed to delete post');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('An error occurred while deleting the post: ' + error.message);
|
|
})
|
|
.finally(() => {
|
|
// Restore button state
|
|
btn.innerHTML = originalText;
|
|
btn.disabled = false;
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|