Files
moto-adv-website/app/templates/community/post_detail.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

918 lines
44 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ post.title }} - Moto Adventure{% endblock %}
{% block head %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""/>
<style>
.map-container {
height: 400px !important;
min-height: 400px;
width: 100%;
border-radius: 1rem;
overflow: hidden;
border: 2px solid #e5e7eb;
background-color: #f8f9fa;
position: relative;
}
/* Ensure Leaflet map fills container */
.leaflet-container {
height: 100% !important;
width: 100% !important;
background-color: #f8f9fa;
}
/* Interactive map specific styling */
#interactive-map {
height: 400px !important;
min-height: 400px;
width: 100%;
border-radius: 1rem;
overflow: hidden;
border: 2px solid #e5e7eb;
position: relative;
background-color: #f8f9fa;
}
/* Force map to be visible */
#interactive-map .leaflet-container {
height: 400px !important;
width: 100% !important;
}
/* Custom div icon styling */
.custom-div-icon {
background: transparent !important;
border: none !important;
}
/* Expanded map modal styling */
#expanded-map {
height: 100%;
min-height: 70vh;
border-radius: 0.5rem;
}
/* Interactive map container */
#interactive-map {
height: 400px;
border-radius: 1rem;
overflow: hidden;
border: 2px solid #e5e7eb;
position: relative;
}
/* Modal animations */
#expandedMapModal {
backdrop-filter: blur(4px);
}
#expandedMapModal.hidden {
opacity: 0;
pointer-events: none;
}
/* Loading indicator styling */
.map-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(255, 255, 255, 0.95);
padding: 1rem 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
z-index: 1000;
}
/* Custom leaflet control styling */
.leaflet-control-zoom {
border: none !important;
box-shadow: 0 2px 5px 0 rgba(0,0,0,0.16) !important;
}
.leaflet-control-zoom a {
border-radius: 4px !important;
border: 1px solid #ddd !important;
background-color: white !important;
color: #333 !important;
}
.leaflet-control-zoom a:hover {
background-color: #f5f5f5 !important;
}
/* Ensure map tiles load properly */
.leaflet-container {
background-color: #f8f9fa;
}
/* Leaflet popup custom styling */
.leaflet-popup-content-wrapper {
border-radius: 8px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
}
.leaflet-popup-content {
margin: 8px 12px;
line-height: 1.4;
}
/* Custom zoom control styling */
.leaflet-control-zoom {
border: none !important;
border-radius: 8px !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;
}
.leaflet-control-zoom a {
border-radius: 4px !important;
color: #374151 !important;
font-weight: bold !important;
}
.leaflet-control-zoom a:hover {
background-color: #f3f4f6 !important;
color: #1f2937 !important;
}
/* Scale control styling */
.leaflet-control-scale {
background-color: rgba(255, 255, 255, 0.9) !important;
border-radius: 4px !important;
padding: 2px 6px !important;
font-size: 11px !important;
}
/* Map loading indicator */
.map-loading {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 1000;
background: rgba(255, 255, 255, 0.9);
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
</style>
{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 pt-16">
<!-- Hero Section -->
<div class="relative overflow-hidden py-16">
{% set cover_image = post.images.filter_by(is_cover=True).first() %}
{% if cover_image %}
<div class="absolute inset-0">
<img src="{{ cover_image.get_url() }}" alt="{{ post.title }}"
class="w-full h-full object-cover opacity-30">
</div>
{% endif %}
<!-- Status Badge -->
{% if current_user.is_authenticated and (current_user.id == post.author_id or current_user.is_admin) %}
<div class="absolute top-4 right-4 z-10">
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium
{{ 'bg-green-100 text-green-800' if post.published else 'bg-yellow-100 text-yellow-800' }}">
{% if post.published %}
<i class="fas fa-check-circle mr-1"></i> Published
{% else %}
<i class="fas fa-clock mr-1"></i> Pending Review
{% endif %}
</span>
</div>
{% endif %}
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<!-- Difficulty Badge -->
<div class="inline-flex items-center px-4 py-2 rounded-full bg-white/10 backdrop-blur-sm border border-white/20 text-white mb-6">
{% for i in range(post.difficulty) %}
<i class="fas fa-star text-yellow-400 mr-1"></i>
{% endfor %}
<span class="ml-2 font-semibold">{{ post.get_difficulty_label() }}</span>
</div>
<h1 class="text-4xl md:text-6xl font-bold text-white mb-4">{{ post.title }}</h1>
{% if post.subtitle %}
<p class="text-xl text-blue-100 max-w-3xl mx-auto">{{ post.subtitle }}</p>
{% endif %}
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12">
<!-- Post Meta Information -->
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20 mb-8 -mt-8 relative z-10">
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div class="flex items-center space-x-4">
<div class="w-12 h-12 bg-gradient-to-r from-orange-500 to-red-600 rounded-full flex items-center justify-center text-white font-bold text-lg">
{{ post.author.nickname[0].upper() }}
</div>
<div>
<h3 class="text-white font-semibold text-lg">{{ post.author.nickname }}</h3>
<p class="text-blue-200 text-sm">
<i class="fas fa-calendar-alt mr-1"></i>
Published on {{ post.created_at.strftime('%B %d, %Y') }}
</p>
</div>
</div>
<div class="flex flex-wrap gap-3">
<span class="inline-flex items-center px-3 py-1 rounded-full bg-pink-500/20 text-pink-200 text-sm">
<i class="fas fa-heart mr-1"></i> {{ post.get_like_count() }} likes
</span>
<span class="inline-flex items-center px-3 py-1 rounded-full bg-blue-500/20 text-blue-200 text-sm">
<i class="fas fa-comments mr-1"></i> {{ post.comments.count() }} comments
</span>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
<!-- Main Content -->
<div class="lg:col-span-2">
<!-- Adventure Story Blog Post -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-blue-600 to-purple-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-book-open text-2xl mr-3"></i>
<div>
<h2 class="text-2xl font-bold">Adventure Story</h2>
<p class="text-blue-100">Follow the journey step by step</p>
</div>
</div>
</div>
<div class="p-8">
<div class="prose prose-lg max-w-none">
{% set content_sections = post.content.split('\n\n') %}
{% set section_images = post.images.filter_by(is_cover=False).all() %}
{% set images_per_section = (section_images|length / (content_sections|length))|round|int if content_sections|length > 0 else 0 %}
{% for section in content_sections %}
{% if section.strip() %}
<div class="mb-8 pb-6 {% if not loop.last %}border-b border-gray-200{% endif %}">
{% set section_text = section.strip() %}
<!-- Extract keywords and clean text -->
{% set keywords = [] %}
{% set clean_text = section_text %}
<!-- Process **keyword** patterns and standalone keywords -->
{% if '**' in section_text %}
{% set parts = section_text.split('**') %}
{% set clean_parts = [] %}
{% for i in range(parts|length) %}
{% if i % 2 == 1 %}
<!-- This is a keyword between ** ** -->
{% set _ = keywords.append(parts[i].strip()) %}
{% else %}
<!-- This is regular text -->
{% set _ = clean_parts.append(parts[i]) %}
{% endif %}
{% endfor %}
{% set clean_text = clean_parts|join(' ')|trim %}
{% endif %}
<!-- Also extract standalone keywords (before <br><br>) -->
{% if '<br>' in clean_text %}
{% set text_parts = clean_text.split('<br>') %}
{% set first_part = text_parts[0].strip() %}
<!-- If first part looks like keywords (short words), extract them -->
{% if first_part and first_part|length < 100 and ' ' in first_part %}
{% set potential_keywords = first_part.split() %}
{% if potential_keywords|length <= 5 %}
<!-- Likely keywords, add them and remove from text -->
{% for kw in potential_keywords %}
{% if kw.strip() %}
{% set _ = keywords.append(kw.strip()) %}
{% endif %}
{% endfor %}
<!-- Keep only text after the first <br><br> -->
{% set remaining_parts = text_parts[1:] %}
{% set clean_text = remaining_parts|join('<br>')|trim %}
{% endif %}
{% endif %}
{% endif %}
<!-- Clean up multiple <br> tags -->
{% set clean_text = clean_text.replace('<br><br><br>', '<br><br>').replace('<br> <br>', '<br><br>').strip() %}
<!-- Section Keywords -->
{% if keywords %}
<div class="mb-4">
{% for keyword in keywords %}
{% if keyword.strip() %}
<span class="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-gradient-to-r from-amber-500 to-orange-500 text-white mr-2 mb-2">
<i class="fas fa-tag mr-1 text-xs"></i>
{{ keyword.strip() }}
</span>
{% endif %}
{% endfor %}
</div>
{% endif %}
<!-- Section Text -->
<div class="text-gray-700 leading-relaxed mb-6 text-lg">
{{ clean_text | safe | nl2br }}
</div>
<!-- Section Images -->
{% if section_images %}
{% set start_idx = loop.index0 * images_per_section %}
{% set end_idx = start_idx + images_per_section %}
{% set current_section_images = section_images[start_idx:end_idx] %}
{% if current_section_images %}
<div class="grid grid-cols-1 {% if current_section_images|length > 1 %}md:grid-cols-2{% endif %} gap-4 mb-6">
{% for image in current_section_images %}
<div class="relative rounded-xl overflow-hidden cursor-pointer group transition-all duration-300 hover:shadow-lg"
onclick="openImageModal('{{ image.get_url() }}', '{{ image.description or image.original_name }}')">
<img src="{{ image.get_url() }}" alt="{{ image.description or image.original_name }}"
class="w-full h-64 object-cover transition-transform duration-300 group-hover:scale-105">
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
{% if image.description %}
<div class="absolute bottom-0 left-0 right-0 p-3 text-white text-sm opacity-0 group-hover:opacity-100 transition-opacity duration-300">
{{ image.description }}
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endif %}
{% endif %}
</div>
{% endif %}
{% endfor %}
</div>
</div>
</div>
<!-- Comments Section -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-purple-600 to-pink-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-comment-dots text-2xl mr-3"></i>
<div>
<h2 class="text-2xl font-bold">Community Discussion</h2>
<p class="text-purple-100">Share your thoughts and experiences ({{ comments|length }})</p>
</div>
</div>
</div>
<div class="p-6">
{% if current_user.is_authenticated %}
<form method="POST" action="{{ url_for('community.add_comment', id=post.id) }}" class="mb-6">
{{ form.hidden_tag() }}
<div class="mb-4">
{{ form.content.label(class="block text-sm font-medium text-gray-700 mb-2") }}
{{ form.content(class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent", rows="4", placeholder="Share your thoughts about this adventure...") }}
</div>
<button type="submit" class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white font-semibold rounded-lg hover:from-blue-700 hover:to-purple-700 transition-all duration-200">
<i class="fas fa-paper-plane mr-2"></i> Post Comment
</button>
</form>
{% else %}
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
<div class="flex items-center">
<i class="fas fa-info-circle text-blue-600 mr-3"></i>
<div>
<p class="text-blue-800">
<a href="{{ url_for('auth.login') }}" class="font-semibold hover:underline">Login</a> or
<a href="{{ url_for('auth.register') }}" class="font-semibold hover:underline">create an account</a>
to join the discussion.
</p>
</div>
</div>
</div>
{% endif %}
<div class="space-y-4">
{% for comment in comments %}
<div class="bg-gray-50 rounded-lg p-4 border-l-4 border-blue-500">
<div class="flex items-center justify-between mb-2">
<h4 class="font-semibold text-gray-900">{{ comment.author.nickname }}</h4>
<span class="text-sm text-gray-500">
{{ comment.created_at.strftime('%B %d, %Y at %I:%M %p') }}
</span>
</div>
<p class="text-gray-700">{{ comment.content }}</p>
</div>
{% endfor %}
{% if comments|length == 0 %}
<div class="text-center py-8 text-gray-500">
<i class="fas fa-comment-slash text-4xl mb-4 opacity-50"></i>
<h5 class="text-lg font-semibold mb-2">No comments yet</h5>
<p>Be the first to share your thoughts about this adventure!</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Sidebar -->
<div class="space-y-8">
<!-- GPS Map and Route Information -->
{% if post.gpx_files.count() > 0 %}
<!-- Map Card (single route, styled like Adventure Info) -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden mb-8">
<div class="bg-gradient-to-r from-blue-600 to-purple-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-map-marked-alt text-2xl mr-3"></i>
<div>
<h2 class="text-xl font-bold">Map</h2>
<p class="text-blue-100">Full trip view</p>
</div>
</div>
</div>
<div class="p-0" style="height:400px;overflow:hidden;">
<iframe
id="route-map-iframe"
src="{{ url_for('static', filename='map_iframe_single.html') }}?route_id={{ post.gpx_files[0].id }}"
width="100%" height="400" style="border:0; border-radius:1rem; display:block; background:#f8f9fa;"
allowfullscreen loading="lazy" referrerpolicy="no-referrer-when-downgrade">
</iframe>
</div>
</div>
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-orange-600 to-red-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-route text-2xl mr-3"></i>
<div>
<h2 class="text-xl font-bold">Route Map</h2>
<p class="text-orange-100">GPS track and statistics</p>
</div>
</div>
</div>
<div class="p-6">
<div id="map" class="map-container mb-6 shadow-lg"></div>
<!-- Route Statistics -->
<div class="grid grid-cols-2 gap-4 mb-6">
{% set gpx_file = post.gpx_files.first() %}
<div class="text-center p-4 bg-gradient-to-br from-blue-50 to-blue-100 rounded-lg">
<div class="text-2xl font-bold text-blue-600" id="distance">
{{ gpx_file.total_distance if gpx_file and gpx_file.total_distance > 0 else '-' }}
</div>
<div class="text-sm text-gray-600">Distance (km)</div>
</div>
<div class="text-center p-4 bg-gradient-to-br from-green-50 to-green-100 rounded-lg">
<div class="text-2xl font-bold text-green-600" id="elevation-gain">
{{ gpx_file.elevation_gain|int if gpx_file and gpx_file.elevation_gain > 0 else '-' }}
</div>
<div class="text-sm text-gray-600">Elevation Gain (m)</div>
</div>
<div class="text-center p-4 bg-gradient-to-br from-purple-50 to-purple-100 rounded-lg">
<div class="text-2xl font-bold text-purple-600" id="max-elevation">
{{ gpx_file.max_elevation|int if gpx_file and gpx_file.max_elevation > 0 else '-' }}
</div>
<div class="text-sm text-gray-600">Max Elevation (m)</div>
</div>
<div class="text-center p-4 bg-gradient-to-br from-orange-50 to-orange-100 rounded-lg">
<div class="text-2xl font-bold text-orange-600" id="waypoints">
{{ gpx_file.total_points if gpx_file and gpx_file.total_points > 0 else '-' }}
</div>
<div class="text-sm text-gray-600">Track Points</div>
</div>
</div>
<!-- Action Buttons -->
<div class="space-y-3">
{% for gpx_file in post.gpx_files %}
{% if current_user.is_authenticated %}
<a href="{{ gpx_file.get_url() }}" download="{{ gpx_file.original_name }}"
class="block w-full text-center px-4 py-2 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-download mr-2"></i>
Download GPX ({{ "%.1f"|format(gpx_file.size / 1024) }} KB)
</a>
{% else %}
<a href="{{ url_for('auth.login') }}"
class="block w-full text-center px-4 py-2 bg-gray-400 text-white font-semibold rounded-lg hover:bg-gray-500 transition-all duration-200">
<i class="fas fa-lock mr-2"></i>
Login to Download GPX
</a>
{% endif %}
{% endfor %}
{% if current_user.is_authenticated %}
<button onclick="toggleLike({{ post.id }})"
class="w-full px-4 py-2 bg-gradient-to-r from-pink-600 to-red-600 text-white font-semibold rounded-lg hover:from-pink-700 hover:to-red-700 transition-all duration-200 transform hover:scale-105">
<i class="fas fa-heart mr-2"></i>
<span id="like-text">Like this Adventure</span>
</button>
{% endif %}
</div>
</div>
</div>
<!-- Interactive Map Card -->
<!-- Interactive Map Card removed as requested -->
{% endif %}
<!-- Adventure Info -->
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-indigo-600 to-blue-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-info-circle text-2xl mr-3"></i>
<div>
<h2 class="text-xl font-bold">Adventure Info</h2>
<p class="text-indigo-100">Trip details</p>
</div>
</div>
</div>
<div class="p-6">
<div class="space-y-4">
<div class="flex items-center justify-between">
<span class="text-gray-600">Difficulty</span>
<div class="flex items-center">
{% for i in range(post.difficulty) %}
<i class="fas fa-star text-yellow-500"></i>
{% endfor %}
{% for i in range(5 - post.difficulty) %}
<i class="far fa-star text-gray-300"></i>
{% endfor %}
</div>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">Published</span>
<span class="text-gray-900">{{ post.created_at.strftime('%B %d, %Y') }}</span>
</div>
<div class="flex items-center justify-between">
<span class="text-gray-600">Author</span>
<span class="text-gray-900">{{ post.author.nickname }}</span>
</div>
{% if post.images.count() > 0 %}
<div class="flex items-center justify-between">
<span class="text-gray-600">Photos</span>
<span class="text-gray-900">{{ post.images.count() }}</span>
</div>
<!-- Interactive Map Card (iframe, styled as in community index) -->
<div class="map-card-square mt-8 mb-8 mx-auto">
<div class="map-card-header">
<span class="map-card-title">🗺️ Interactive Route Map</span>
<span class="map-card-count">1 route</span>
</div>
</div>
{% endif %}
{% if post.gpx_files.count() > 0 %}
<div class="flex items-center justify-between">
<span class="text-gray-600">GPS Files</span>
<span class="text-gray-900">{{ post.gpx_files.count() }}</span>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Photo Gallery (Compact) -->
{% if post.images.count() > 0 %}
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="bg-gradient-to-r from-green-600 to-teal-600 p-4">
<div class="flex items-center text-white">
<i class="fas fa-camera-retro text-xl mr-2"></i>
<div>
<h2 class="text-lg font-bold">Photo Gallery</h2>
<p class="text-green-100 text-sm">{{ post.images.count() }} photos</p>
</div>
</div>
</div>
<div class="p-4">
<div class="grid grid-cols-2 gap-2">
{% for image in post.images %}
<div class="relative rounded-lg overflow-hidden cursor-pointer group"
onclick="openImageModal('{{ image.get_url() }}', '{{ image.description or image.original_name }}')">
<img src="{{ image.get_url() }}" alt="{{ image.description or image.original_name }}"
class="w-full h-20 object-cover transition-transform duration-300 group-hover:scale-110">
{% if image.is_cover %}
<div class="absolute top-1 left-1">
<span class="inline-flex items-center px-1 py-0.5 rounded text-xs font-semibold bg-yellow-500 text-white">
<i class="fas fa-star"></i>
</span>
</div>
{% endif %}
<div class="absolute inset-0 bg-black/30 opacity-0 group-hover:opacity-100 transition-opacity duration-300"></div>
</div>
{% endfor %}
</div>
{% if post.images.count() > 4 %}
<div class="mt-3 text-center">
<span class="text-sm text-gray-500">Click any photo to view gallery</span>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Image Modal -->
<div id="imageModal" class="fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50 hidden">
<div class="max-w-4xl max-h-full p-4">
<div class="relative">
<button onclick="closeImageModal()"
class="absolute top-2 right-2 text-white bg-black bg-opacity-50 rounded-full w-8 h-8 flex items-center justify-center hover:bg-opacity-75 z-10">
<i class="fas fa-times"></i>
</button>
<img id="modalImage" src="" alt="" class="max-w-full max-h-screen object-contain rounded-lg">
<div id="modalCaption" class="text-white text-center mt-2 px-4"></div>
</div>
</div>
</div>
<!-- Expanded Map Modal -->
<div id="expandedMapModal" class="fixed inset-0 bg-black bg-opacity-90 flex items-center justify-center z-50 hidden">
<div class="w-full h-full p-4 md:p-8 flex flex-col">
<div class="flex justify-end mb-4">
<button onclick="closeExpandedMap()"
class="text-white bg-black bg-opacity-50 rounded-full w-12 h-12 flex items-center justify-center hover:bg-opacity-75 transition-all duration-200">
<i class="fas fa-times text-xl"></i>
</button>
</div>
<div class="flex-1 bg-white rounded-lg overflow-hidden shadow-2xl">
<div id="expanded-map" class="w-full h-full"></div>
</div>
</div>
</div>
<!-- Chat Discussion Widget -->
{% include 'chat/embed.html' %}
{% endblock %}
{% block scripts %}
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
crossorigin=""></script>
<script>
// Global variables
let map;
let interactiveMap;
let expandedMap;
let gpxPolyline;
let trackPointsData = [];
// Simple test to check if Leaflet is loaded
console.log('Leaflet loaded:', typeof L !== 'undefined');
document.addEventListener('DOMContentLoaded', function() {
console.log('DOM Content Loaded - Starting simple map test...');
// Simple map initialization
setTimeout(function() {
console.log('Attempting to create basic map...');
try {
// Create a very basic map
const mapContainer = document.getElementById('interactive-map');
if (!mapContainer) {
console.error('Map container not found');
return;
}
console.log('Container found, creating map...');
// Clear any existing content
mapContainer.innerHTML = '';
// Create map with basic settings
interactiveMap = L.map('interactive-map').setView([45.9432, 24.9668], 6);
console.log('Map created, adding tiles...');
// Add OpenStreetMap tiles
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(interactiveMap);
console.log('Tiles added, adding test marker...');
// Add a test marker
L.marker([45.9432, 24.9668])
.addTo(interactiveMap)
.bindPopup('🗺️ Interactive Map Test - Romania')
.openPopup();
console.log('✅ Basic map setup complete!');
// Hide loading indicator
const loadingDiv = document.getElementById('map-loading');
if (loadingDiv) {
loadingDiv.style.display = 'none';
}
// Hide fallback content
const fallbackDiv = document.getElementById('map-fallback');
if (fallbackDiv) {
fallbackDiv.style.display = 'none';
}
// Force resize
setTimeout(() => {
interactiveMap.invalidateSize();
console.log('Map size invalidated');
}, 500);
} catch (error) {
console.error('❌ Error creating map:', error);
// Show error in the container
const mapContainer = document.getElementById('interactive-map');
if (mapContainer) {
mapContainer.innerHTML = `
<div class="flex items-center justify-center h-full bg-red-50 text-red-600 p-4">
<div class="text-center">
<i class="fas fa-exclamation-triangle text-2xl mb-2"></i>
<div class="font-semibold">Map Loading Error</div>
<div class="text-sm">${error.message}</div>
</div>
</div>
`;
}
}
}, 1000);
});
// Global functions that need to be available regardless of GPX files
function expandMap() {
console.log('Expand map button clicked');
const modal = document.getElementById('expandedMapModal');
if (!modal) {
console.error('Expanded map modal not found');
return;
}
modal.classList.remove('hidden');
// Initialize expanded map after modal is shown
setTimeout(() => {
if (!expandedMap) {
console.log('Initializing expanded map...');
expandedMap = L.map('expanded-map', {
zoomControl: true,
scrollWheelZoom: true,
doubleClickZoom: true,
boxZoom: true,
dragging: true
}).setView([45.9432, 24.9668], 6);
// Add tile layer with scale control
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
maxZoom: 18,
minZoom: 2
}).addTo(expandedMap);
// Add scale control
L.control.scale({
position: 'bottomleft',
metric: true,
imperial: false
}).addTo(expandedMap);
}
// Clear existing layers except tile layer
expandedMap.eachLayer(function(layer) {
if (layer instanceof L.Polyline || layer instanceof L.Marker) {
expandedMap.removeLayer(layer);
}
});
// Add the GPX track to expanded map if we have track data
if (trackPointsData && trackPointsData.length > 0) {
console.log('Adding track data to expanded map...');
const expandedPolyline = L.polyline(trackPointsData, {
color: '#ef4444',
weight: 5,
opacity: 0.9,
smoothFactor: 1
}).addTo(expandedMap);
// Fit map to track
expandedMap.fitBounds(expandedPolyline.getBounds(), {
padding: [30, 30],
maxZoom: 15
});
// Add enhanced markers
const startIcon = L.divIcon({
html: '<div class="w-8 h-8 bg-green-500 rounded-full border-2 border-white flex items-center justify-center text-white shadow-lg"><i class="fas fa-play"></i></div>',
className: 'custom-div-icon',
iconSize: [32, 32],
iconAnchor: [16, 16]
});
const endIcon = L.divIcon({
html: '<div class="w-8 h-8 bg-red-500 rounded-full border-2 border-white flex items-center justify-center text-white shadow-lg"><i class="fas fa-flag-checkered"></i></div>',
className: 'custom-div-icon',
iconSize: [32, 32],
iconAnchor: [16, 16]
});
L.marker(trackPointsData[0], { icon: startIcon })
.bindPopup('<strong>🚀 Start Point</strong>')
.addTo(expandedMap);
L.marker(trackPointsData[trackPointsData.length - 1], { icon: endIcon })
.bindPopup('<strong>🏁 End Point</strong>')
.addTo(expandedMap);
} else {
console.log('No track data available for expanded map');
// Show a message on the map
L.popup()
.setLatLng([45.9432, 24.9668])
.setContent('<div class="text-gray-600">📍 No GPX route data available</div>')
.openOn(expandedMap);
}
// Invalidate size to ensure proper rendering
expandedMap.invalidateSize();
console.log('✓ Expanded map setup complete');
}, 100);
}
function closeExpandedMap() {
const modal = document.getElementById('expandedMapModal');
if (modal) {
modal.classList.add('hidden');
}
}
// Close expanded map on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeExpandedMap();
}
});
// Image modal functionality
function openImageModal(imageSrc, imageTitle) {
document.getElementById('modalImage').src = imageSrc;
document.getElementById('modalCaption').textContent = imageTitle;
document.getElementById('imageModal').classList.remove('hidden');
}
function closeImageModal() {
document.getElementById('imageModal').classList.add('hidden');
}
// Close modal on click outside
document.getElementById('imageModal').addEventListener('click', function(e) {
if (e.target === this) {
closeImageModal();
}
});
// Close modal on escape key
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeImageModal();
}
});
// Like functionality
function toggleLike(postId) {
fetch(`/community/post/${postId}/like`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
const likeBtn = document.querySelector('button[onclick*="toggleLike"]');
const likeText = document.getElementById('like-text');
if (data.liked) {
likeBtn.classList.remove('from-pink-600', 'to-red-600', 'hover:from-pink-700', 'hover:to-red-700');
likeBtn.classList.add('from-red-600', 'to-pink-600', 'hover:from-red-700', 'hover:to-pink-700');
likeText.textContent = 'Liked!';
} else {
likeBtn.classList.remove('from-red-600', 'to-pink-600', 'hover:from-red-700', 'hover:to-pink-700');
likeBtn.classList.add('from-pink-600', 'to-red-600', 'hover:from-pink-700', 'hover:to-red-700');
likeText.textContent = 'Like this Adventure';
}
// Update like count in meta section
const likeCountSpan = document.querySelector('.bg-pink-500\\/20');
if (likeCountSpan) {
likeCountSpan.innerHTML = `<i class="fas fa-heart mr-1"></i> ${data.count} likes`;
}
})
.catch(error => {
console.error('Error:', error);
alert('Please log in to like posts');
});
}
</script>
{% endblock %}