Major Feature Update: Modern Chat System & Admin Management

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.
This commit is contained in:
ske087
2025-08-10 00:22:33 +03:00
parent 1661f5f588
commit 30bd4c62ad
20 changed files with 3649 additions and 349 deletions

View File

@@ -0,0 +1,243 @@
{% extends "base.html" %}
{% block title %}Create Chat Room{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 pt-16">
<!-- Header Section -->
<div class="relative overflow-hidden py-12">
<div class="absolute inset-0 bg-black/20"></div>
<div class="relative max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
<h1 class="text-3xl font-bold text-white mb-2">
<i class="fas fa-plus-circle mr-3"></i>Create New Chat Room
</h1>
<p class="text-blue-200">Start a discussion with the motorcycle community</p>
</div>
</div>
</div>
<div class="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-12 -mt-6">
<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 justify-between text-white">
<div class="flex items-center">
<i class="fas fa-comments text-2xl mr-3"></i>
<div>
<h2 class="text-xl font-bold">Room Configuration</h2>
<p class="text-green-100 text-sm">Set up your chat room details</p>
</div>
</div>
<a href="{{ url_for('chat.index') }}" class="bg-white/20 hover:bg-white/30 px-4 py-2 rounded-lg transition-all duration-200">
<i class="fas fa-arrow-left mr-2"></i>Back to Chat
</a>
</div>
</div>
<div class="p-8">
<form method="POST" action="{{ url_for('chat.create_room') }}" class="space-y-6">
<!-- Room Name -->
<div class="space-y-2">
<label for="room_name" class="block text-sm font-semibold text-gray-700">
<i class="fas fa-tag mr-2 text-green-600"></i>Room Name *
</label>
<input type="text" class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all duration-200"
id="room_name" name="room_name"
placeholder="Enter a descriptive room name" required maxlength="100">
<p class="text-xs text-gray-500">Choose a clear, descriptive name for your chat room</p>
</div>
<!-- Description -->
<div class="space-y-2">
<label for="description" class="block text-sm font-semibold text-gray-700">
<i class="fas fa-align-left mr-2 text-green-600"></i>Description
</label>
<textarea class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all duration-200"
id="description" name="description" rows="3"
placeholder="Describe what this room is about..." maxlength="500"></textarea>
<p class="text-xs text-gray-500">Optional: Help others understand the room's purpose</p>
</div>
<!-- Post Binding Section -->
<div class="space-y-4 p-6 bg-blue-50 rounded-xl border border-blue-200">
<div class="flex items-center">
<i class="fas fa-link mr-3 text-blue-600 text-lg"></i>
<h3 class="text-lg font-semibold text-gray-800">Link to Post (Optional)</h3>
</div>
<div class="space-y-2">
<label for="related_post_id" class="block text-sm font-semibold text-gray-700">
Select a post to discuss
</label>
<select class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all duration-200"
id="related_post_id" name="related_post_id">
<option value="">No specific post - General discussion</option>
{% for post in posts %}
<option value="{{ post.id }}" {% if pre_selected_post and post.id == pre_selected_post %}selected{% endif %}>
{{ post.title }} - by {{ post.author.nickname }}
</option>
{% endfor %}
</select>
<p class="text-xs text-gray-500">
<i class="fas fa-info-circle mr-1"></i>
Link this room to a specific post for focused discussions
</p>
</div>
</div>
<!-- Room Type -->
<div class="space-y-2">
<label for="room_type" class="block text-sm font-semibold text-gray-700">
<i class="fas fa-folder mr-2 text-green-600"></i>Room Category
</label>
<select class="w-full px-4 py-3 border border-gray-300 rounded-xl focus:ring-2 focus:ring-green-500 focus:border-transparent transition-all duration-200"
id="room_type" name="room_type">
<option value="general">General Discussion</option>
<option value="technical">Technical Support</option>
<option value="social">Social Chat</option>
<option value="post_discussion">Post Discussion</option>
</select>
<p class="text-xs text-gray-500">Category will auto-update based on post selection</p>
</div>
<!-- Privacy Setting -->
<div class="space-y-3 p-6 bg-amber-50 rounded-xl border border-amber-200">
<div class="flex items-center">
<i class="fas fa-shield-alt mr-3 text-amber-600 text-lg"></i>
<h3 class="text-lg font-semibold text-gray-800">Privacy Settings</h3>
</div>
<div class="flex items-start space-x-3">
<input class="mt-1 w-4 h-4 text-amber-600 border-gray-300 rounded focus:ring-amber-500"
type="checkbox" id="is_private" name="is_private">
<div>
<label class="block text-sm font-medium text-gray-700" for="is_private">
Make this room private
</label>
<p class="text-xs text-gray-500 mt-1">
Private rooms are only visible to invited members. Public rooms can be joined by anyone.
</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-4 pt-6">
<a href="{{ url_for('chat.index') }}"
class="flex-1 px-6 py-3 bg-gray-100 text-gray-700 font-semibold rounded-xl hover:bg-gray-200 transition-all duration-200 text-center">
<i class="fas fa-times mr-2"></i>Cancel
</a>
<button type="submit"
class="flex-1 px-6 py-3 bg-gradient-to-r from-green-600 to-emerald-600 text-white font-semibold rounded-xl hover:from-green-700 hover:to-emerald-700 transition-all duration-200">
<i class="fas fa-plus mr-2"></i>Create Chat Room
</button>
</div>
</form>
</div>
</div>
<!-- Recent Posts Preview -->
{% if posts %}
<div class="mt-8 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-newspaper text-2xl mr-3"></i>
<div>
<h3 class="text-xl font-bold">Recent Community Posts</h3>
<p class="text-blue-100 text-sm">Available for discussion rooms</p>
</div>
</div>
</div>
<div class="p-6">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{% for post in posts[:6] %}
<div class="bg-gray-50 rounded-xl p-4 hover:bg-gray-100 transition-all duration-200 cursor-pointer post-preview"
data-post-id="{{ post.id }}" data-post-title="{{ post.title }}">
<h4 class="font-semibold text-gray-800 mb-2 line-clamp-2">
{{ post.title }}
</h4>
<p class="text-gray-600 text-sm mb-3 line-clamp-3">
{{ post.content[:120] }}{% if post.content|length > 120 %}...{% endif %}
</p>
<div class="flex items-center justify-between text-xs text-gray-500">
<span>by {{ post.author.nickname }}</span>
<span>{{ post.created_at.strftime('%m/%d') }}</span>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<script>
// Auto-update room type when post is selected
document.getElementById('related_post_id').addEventListener('change', function() {
const roomTypeSelect = document.getElementById('room_type');
if (this.value) {
roomTypeSelect.value = 'post_discussion';
document.getElementById('room_name').placeholder = 'Discussion: ' + this.options[this.selectedIndex].text.split(' - ')[0];
} else {
roomTypeSelect.value = 'general';
document.getElementById('room_name').placeholder = 'Enter a descriptive room name';
}
});
// Post preview selection
document.querySelectorAll('.post-preview').forEach(preview => {
preview.addEventListener('click', function() {
const postId = this.dataset.postId;
const postTitle = this.dataset.postTitle;
// Update the select dropdown
document.getElementById('related_post_id').value = postId;
// Update room name suggestion
document.getElementById('room_name').value = `Discussion: ${postTitle}`;
// Update room type
document.getElementById('room_type').value = 'post_discussion';
// Visual feedback
document.querySelectorAll('.post-preview').forEach(p => p.classList.remove('ring-2', 'ring-blue-500', 'bg-blue-100'));
this.classList.add('ring-2', 'ring-blue-500', 'bg-blue-100');
// Scroll to form
document.querySelector('form').scrollIntoView({ behavior: 'smooth' });
});
});
// Form validation
document.querySelector('form').addEventListener('submit', function(e) {
const roomName = document.getElementById('room_name').value.trim();
if (!roomName) {
e.preventDefault();
alert('Please enter a room name');
document.getElementById('room_name').focus();
}
});
</script>
<style>
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
.post-preview:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
</style>
{% endblock %}

View File

@@ -48,60 +48,41 @@
</div>
<!-- Quick Actions -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl hover:scale-105">
<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-life-ring text-3xl mr-4"></i>
<div>
<h4 class="text-lg font-bold">Get Support</h4>
<p class="text-blue-100 text-sm">Need help or password reset?</p>
</div>
</div>
</div>
<div class="p-6">
<p class="text-gray-600 mb-4">Contact administrators for support or password reset</p>
<a href="{{ url_for('chat.support') }}"
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-headset mr-2"></i>Get Support
</a>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8 max-w-4xl mx-auto">
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl hover:scale-105">
<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-plus-circle text-3xl mr-4"></i>
<div>
<h4 class="text-lg font-bold">Create Room</h4>
<p class="text-green-100 text-sm">Start a new discussion</p>
<h4 class="text-xl font-bold">Create Chat Room</h4>
<p class="text-green-100">Start a new discussion</p>
</div>
</div>
</div>
<div class="p-6">
<p class="text-gray-600 mb-4">Start a new chat room on any motorcycle topic</p>
<div class="p-6 text-center">
<p class="text-gray-600 mb-6">Start a new chat room on any motorcycle topic or connect it to a specific post</p>
<a href="{{ url_for('chat.create_room_form') }}"
class="inline-flex items-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">
<i class="fas fa-plus mr-2"></i>Create Room
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">
<i class="fas fa-plus mr-2"></i>Create New Room
</a>
</div>
</div>
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl hover:scale-105">
<div class="bg-gradient-to-r from-orange-600 to-red-600 p-6">
<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-key text-3xl mr-4"></i>
<i class="fas fa-newspaper text-3xl mr-4"></i>
<div>
<h4 class="text-lg font-bold">Password Reset</h4>
<p class="text-orange-100 text-sm">Forgot your password?</p>
<h4 class="text-xl font-bold">Post Discussions</h4>
<p class="text-blue-100">Chat about community posts</p>
</div>
</div>
</div>
<div class="p-6">
<p class="text-gray-600 mb-4">Request a password reset from administrators</p>
<a href="{{ url_for('auth.forgot_password') }}"
class="inline-flex items-center px-4 py-2 bg-gradient-to-r from-orange-600 to-red-600 text-white font-semibold rounded-lg hover:from-orange-700 hover:to-red-700 transition-all duration-200">
<i class="fas fa-unlock-alt mr-2"></i>Reset Password
<div class="p-6 text-center">
<p class="text-gray-600 mb-6">Join discussions about specific community posts and share your thoughts</p>
<a href="{{ url_for('chat.post_discussions') }}"
class="inline-flex items-center px-6 py-3 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-comments mr-2"></i>View Discussions
</a>
</div>
</div>

View File

@@ -0,0 +1,205 @@
{% extends "base.html" %}
{% block title %}Post Discussions - Chat Rooms{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 pt-16">
<!-- Header Section -->
<div class="relative overflow-hidden py-12">
<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 text-center">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-6 border border-white/20">
<h1 class="text-3xl font-bold text-white mb-2">
<i class="fas fa-newspaper mr-3"></i>Post Discussions
</h1>
<p class="text-blue-200">Chat rooms linked to community posts</p>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12 -mt-6">
<!-- Navigation -->
<div class="flex justify-center mb-8">
<div class="bg-white rounded-2xl shadow-xl p-2 flex space-x-2">
<a href="{{ url_for('chat.index') }}"
class="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-all duration-200">
<i class="fas fa-comments mr-2"></i>All Chats
</a>
<span class="px-4 py-2 bg-gradient-to-r from-blue-600 to-purple-600 text-white rounded-lg font-semibold">
<i class="fas fa-newspaper mr-2"></i>Post Discussions
</span>
<a href="{{ url_for('chat.create_room_form') }}"
class="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-all duration-200">
<i class="fas fa-plus mr-2"></i>Create Room
</a>
</div>
</div>
<!-- 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-blue-600 to-cyan-600 p-6">
<div class="flex items-center text-white">
<i class="fas fa-chart-bar text-3xl mr-4"></i>
<div>
<h3 class="text-xl font-bold">{{ total_discussions }}</h3>
<p class="text-blue-100">Total Post Discussions</p>
</div>
</div>
</div>
</div>
<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-fire text-3xl mr-4"></i>
<div>
<h3 class="text-xl font-bold">{{ active_discussions }}</h3>
<p class="text-green-100">Active This Week</p>
</div>
</div>
</div>
</div>
</div>
{% if rooms.items %}
<!-- Discussion Rooms -->
<div class="space-y-6">
{% for room in rooms.items %}
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl">
<div class="p-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<div class="flex-1">
<!-- Room Header -->
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-xl font-bold text-gray-800 mb-2">{{ room.name }}</h3>
{% if room.description %}
<p class="text-gray-600 mb-3">{{ room.description }}</p>
{% endif %}
</div>
<span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-semibold ml-4">
<i class="fas fa-comments mr-1"></i>{{ room.message_count or 0 }} messages
</span>
</div>
<!-- Related Post -->
{% if room.related_post %}
<div class="bg-blue-50 border border-blue-200 rounded-xl p-4 mb-4">
<div class="flex items-start">
<i class="fas fa-link text-blue-600 mr-3 mt-1"></i>
<div class="flex-1">
<h4 class="font-semibold text-blue-800 mb-1">Discussing Post:</h4>
<a href="{{ url_for('community.post_detail', id=room.related_post.id) }}"
class="text-blue-700 hover:text-blue-900 font-medium">
{{ room.related_post.title }}
</a>
<p class="text-blue-600 text-sm mt-1">
by {{ room.related_post.author.nickname }} •
{{ room.related_post.created_at.strftime('%B %d, %Y') }}
</p>
</div>
</div>
</div>
{% endif %}
<!-- Room Info -->
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
<span>
<i class="fas fa-user mr-1"></i>
Created by {{ room.created_by.nickname }}
</span>
<span>
<i class="fas fa-users mr-1"></i>
{{ room.participants.count() }} members
</span>
<span>
<i class="fas fa-clock mr-1"></i>
{% if room.last_activity %}
Last activity {{ room.last_activity.strftime('%m/%d/%Y at %I:%M %p') }}
{% else %}
No recent activity
{% endif %}
</span>
{% if room.is_private %}
<span class="bg-amber-100 text-amber-800 px-2 py-1 rounded-full">
<i class="fas fa-lock mr-1"></i>Private
</span>
{% endif %}
</div>
</div>
<!-- Action Buttons -->
<div class="mt-4 lg:mt-0 lg:ml-6 flex flex-col space-y-2">
<a href="{{ url_for('chat.room', room_id=room.id) }}"
class="inline-flex items-center justify-center px-6 py-3 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-sign-in-alt mr-2"></i>Join Discussion
</a>
{% if room.related_post %}
<a href="{{ url_for('community.post_detail', id=room.related_post.id) }}"
class="inline-flex items-center justify-center px-6 py-2 bg-gray-100 text-gray-700 font-medium rounded-lg hover:bg-gray-200 transition-all duration-200">
<i class="fas fa-eye mr-2"></i>View Post
</a>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<!-- Pagination -->
{% if rooms.pages > 1 %}
<div class="flex justify-center mt-8">
<div class="bg-white rounded-2xl shadow-xl overflow-hidden">
<div class="flex">
{% if rooms.has_prev %}
<a href="{{ url_for('chat.post_discussions', page=rooms.prev_num) }}"
class="px-4 py-2 text-gray-600 hover:bg-gray-100 transition-all duration-200">
<i class="fas fa-chevron-left"></i>
</a>
{% endif %}
{% for page_num in rooms.iter_pages() %}
{% if page_num %}
{% if page_num != rooms.page %}
<a href="{{ url_for('chat.post_discussions', page=page_num) }}"
class="px-4 py-2 text-gray-600 hover:bg-gray-100 transition-all duration-200">
{{ page_num }}
</a>
{% else %}
<span class="px-4 py-2 bg-blue-600 text-white">{{ page_num }}</span>
{% endif %}
{% else %}
<span class="px-4 py-2 text-gray-400">...</span>
{% endif %}
{% endfor %}
{% if rooms.has_next %}
<a href="{{ url_for('chat.post_discussions', page=rooms.next_num) }}"
class="px-4 py-2 text-gray-600 hover:bg-gray-100 transition-all duration-200">
<i class="fas fa-chevron-right"></i>
</a>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% else %}
<!-- Empty State -->
<div class="text-center py-16">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-12 border border-white/20">
<i class="fas fa-newspaper text-6xl text-white/50 mb-6"></i>
<h3 class="text-2xl font-bold text-white mb-4">No post discussions yet</h3>
<p class="text-blue-200 mb-8">Create the first chat room linked to a community post!</p>
<a href="{{ url_for('chat.create_room_form') }}"
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">
<i class="fas fa-plus mr-2"></i>Create Post Discussion
</a>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -0,0 +1,172 @@
{% extends "base.html" %}
{% block title %}{{ post.title }} - Discussions{% endblock %}
{% block content %}
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 pt-16">
<!-- Header Section -->
<div class="relative overflow-hidden py-12">
<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-6 border border-white/20">
<div class="flex items-center justify-between">
<div class="flex-1">
<h1 class="text-2xl md:text-3xl font-bold text-white mb-2">
<i class="fas fa-comments mr-3"></i>Discussions for:
</h1>
<h2 class="text-xl md:text-2xl text-blue-200">{{ post.title }}</h2>
<p class="text-blue-300 text-sm mt-2">
by {{ post.author.nickname }} • {{ post.created_at.strftime('%B %d, %Y') }}
</p>
</div>
<a href="{{ url_for('community.post_detail', id=post.id) }}"
class="bg-white/20 hover:bg-white/30 px-4 py-2 rounded-lg transition-all duration-200 text-white">
<i class="fas fa-eye mr-2"></i>View Post
</a>
</div>
</div>
</div>
</div>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-12 -mt-6">
<!-- Navigation -->
<div class="flex justify-center mb-8">
<div class="bg-white rounded-2xl shadow-xl p-2 flex flex-wrap gap-2">
<a href="{{ url_for('chat.index') }}"
class="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-all duration-200">
<i class="fas fa-comments mr-2"></i>All Chats
</a>
<a href="{{ url_for('chat.post_discussions') }}"
class="px-4 py-2 text-gray-600 hover:text-gray-800 hover:bg-gray-100 rounded-lg transition-all duration-200">
<i class="fas fa-newspaper mr-2"></i>Post Discussions
</a>
<a href="{{ url_for('chat.create_room_form') }}?post_id={{ post.id }}"
class="px-4 py-2 bg-gradient-to-r from-green-600 to-emerald-600 text-white rounded-lg hover:from-green-700 hover:to-emerald-700 transition-all duration-200">
<i class="fas fa-plus mr-2"></i>New Discussion
</a>
</div>
</div>
<!-- Post Summary -->
<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-file-alt text-2xl mr-3"></i>
<div>
<h3 class="text-xl font-bold">Original Post</h3>
<p class="text-blue-100 text-sm">{{ post.created_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
</div>
</div>
</div>
<div class="p-6">
<div class="prose max-w-none">
{{ post.content[:300] }}{% if post.content|length > 300 %}...{% endif %}
</div>
<div class="flex items-center justify-between mt-4 pt-4 border-t border-gray-200">
<div class="flex items-center space-x-4 text-sm text-gray-500">
<span>
<i class="fas fa-user mr-1"></i>{{ post.author.nickname }}
</span>
{% if post.likes %}
<span>
<i class="fas fa-heart mr-1 text-red-500"></i>{{ post.likes.count() }} likes
</span>
{% endif %}
{% if post.comments %}
<span>
<i class="fas fa-comment mr-1 text-blue-500"></i>{{ post.comments.count() }} comments
</span>
{% endif %}
</div>
<a href="{{ url_for('community.post_detail', id=post.id) }}"
class="px-4 py-2 bg-blue-100 text-blue-700 rounded-lg hover:bg-blue-200 transition-all duration-200">
<i class="fas fa-external-link-alt mr-2"></i>Read Full Post
</a>
</div>
</div>
</div>
{% if rooms %}
<!-- Discussion Rooms -->
<div class="space-y-6">
<h2 class="text-2xl font-bold text-white mb-6 flex items-center">
<i class="fas fa-comments mr-3"></i>Discussion Rooms ({{ rooms|length }})
</h2>
{% for room in rooms %}
<div class="bg-white rounded-2xl shadow-xl overflow-hidden transition-all duration-300 hover:shadow-2xl">
<div class="p-6">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<div class="flex-1">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h3 class="text-xl font-bold text-gray-800 mb-2">{{ room.name }}</h3>
{% if room.description %}
<p class="text-gray-600 mb-3">{{ room.description }}</p>
{% endif %}
</div>
<div class="flex space-x-2 ml-4">
<span class="bg-blue-100 text-blue-800 px-3 py-1 rounded-full text-sm font-semibold">
<i class="fas fa-comments mr-1"></i>{{ room.message_count or 0 }}
</span>
{% if room.is_private %}
<span class="bg-amber-100 text-amber-800 px-3 py-1 rounded-full text-sm font-semibold">
<i class="fas fa-lock mr-1"></i>Private
</span>
{% endif %}
</div>
</div>
<div class="flex flex-wrap items-center gap-4 text-sm text-gray-500">
<span>
<i class="fas fa-user mr-1"></i>
Created by {{ room.created_by.nickname }}
</span>
<span>
<i class="fas fa-users mr-1"></i>
{{ room.participants.count() }} members
</span>
<span>
<i class="fas fa-clock mr-1"></i>
{% if room.last_activity %}
{{ room.last_activity.strftime('%m/%d/%Y at %I:%M %p') }}
{% else %}
No recent activity
{% endif %}
</span>
<span>
<i class="fas fa-calendar mr-1"></i>
Created {{ room.created_at.strftime('%m/%d/%Y') }}
</span>
</div>
</div>
<div class="mt-4 lg:mt-0 lg:ml-6">
<a href="{{ url_for('chat.room', room_id=room.id) }}"
class="inline-flex items-center px-6 py-3 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-sign-in-alt mr-2"></i>Join Discussion
</a>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<!-- Empty State -->
<div class="text-center py-16">
<div class="bg-white/10 backdrop-blur-sm rounded-2xl p-12 border border-white/20">
<i class="fas fa-comments text-6xl text-white/50 mb-6"></i>
<h3 class="text-2xl font-bold text-white mb-4">No discussions yet</h3>
<p class="text-blue-200 mb-8">Be the first to start a discussion about this post!</p>
<a href="{{ url_for('chat.create_room_form') }}?post_id={{ post.id }}"
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">
<i class="fas fa-plus mr-2"></i>Start Discussion
</a>
</div>
</div>
{% endif %}
</div>
</div>
{% endblock %}

View File

@@ -2,276 +2,131 @@
{% block title %}{{ room.name }} - Chat{% endblock %}
{% block head %}
<style>
.chat-room-container {
height: calc(100vh - 80px);
display: flex;
flex-direction: column;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.chat-header {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
padding: 1rem;
border-bottom: 1px solid #e0e0e0;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}
.chat-main {
flex: 1;
display: flex;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(10px);
}
.messages-container {
flex: 1;
display: flex;
flex-direction: column;
height: 100%;
}
.messages-area {
flex: 1;
overflow-y: auto;
padding: 1rem;
background: #f8f9fa;
}
.message {
margin-bottom: 1rem;
animation: fadeIn 0.3s ease-in;
}
.message-header {
display: flex;
align-items: center;
margin-bottom: 0.25rem;
}
.message-author {
font-weight: 600;
color: #495057;
margin-right: 0.5rem;
}
.message-time {
font-size: 0.75rem;
color: #6c757d;
}
.message-content {
background: white;
padding: 0.75rem 1rem;
border-radius: 18px;
border: 1px solid #e9ecef;
max-width: 70%;
word-wrap: break-word;
}
.message.own-message {
text-align: right;
}
.message.own-message .message-content {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
margin-left: auto;
}
.message.system-message .message-content {
background: #e9ecef;
color: #6c757d;
font-style: italic;
text-align: center;
max-width: 100%;
}
.message-input-area {
padding: 1rem;
background: white;
border-top: 1px solid #e0e0e0;
}
.message-input {
border: 2px solid #e9ecef;
border-radius: 25px;
padding: 0.75rem 1rem;
font-size: 1rem;
transition: all 0.3s ease;
}
.message-input:focus {
border-color: #667eea;
box-shadow: 0 0 0 0.2rem rgba(102, 126, 234, 0.25);
}
.send-button {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border: none;
border-radius: 50%;
width: 50px;
height: 50px;
color: white;
font-size: 1.2rem;
transition: all 0.3s ease;
}
.send-button:hover {
transform: scale(1.1);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.3);
}
.participants-sidebar {
width: 250px;
background: white;
border-left: 1px solid #e0e0e0;
padding: 1rem;
overflow-y: auto;
}
.participant-item {
display: flex;
align-items: center;
padding: 0.5rem;
margin-bottom: 0.5rem;
border-radius: 10px;
transition: background 0.2s ease;
}
.participant-item:hover {
background: #f8f9fa;
}
.participant-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: 600;
margin-right: 0.75rem;
}
.participant-info {
flex: 1;
}
.participant-name {
font-weight: 600;
color: #495057;
}
.participant-role {
font-size: 0.75rem;
color: #6c757d;
}
.admin-badge {
background: #dc3545;
color: white;
padding: 0.1rem 0.5rem;
border-radius: 10px;
font-size: 0.6rem;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
@media (max-width: 768px) {
.participants-sidebar {
display: none;
}
.message-content {
max-width: 85%;
}
}
</style>
{% endblock %}
{% block content %}
<div class="chat-room-container">
<div class="chat-header">
<div class="d-flex align-items-center justify-content-between">
<div>
<h4 class="mb-0">{{ room.name }}</h4>
<small class="text-muted">{{ room.description or 'No description' }}</small>
{% if room.related_post %}
<br><small class="text-primary">Related to: <a href="#" class="text-primary">{{ room.related_post.title }}</a></small>
{% endif %}
</div>
<div>
<a href="{{ url_for('chat.index') }}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left"></i> Back to Chats
</a>
</div>
</div>
</div>
<div class="chat-main">
<div class="messages-container">
<div class="messages-area" id="messagesArea">
{% for message in messages %}
<div class="message {% if message.user_id == current_user.id %}own-message{% endif %} {% if message.message_type == 'system' %}system-message{% endif %}">
{% if message.message_type != 'system' %}
<div class="message-header">
<span class="message-author">{{ message.user.nickname }}</span>
{% if message.user.is_admin %}
<span class="admin-badge">ADMIN</span>
<!-- Chat Room Header -->
<div class="min-h-screen bg-gradient-to-br from-blue-900 via-purple-900 to-teal-900 py-8">
<div class="max-w-6xl mx-auto px-4">
<!-- Room Header Card -->
<div class="bg-white/10 backdrop-blur-md rounded-2xl p-6 mb-6 border border-white/20">
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between">
<div class="mb-4 lg:mb-0">
<div class="flex items-center mb-2">
<div class="w-12 h-12 bg-gradient-to-r from-blue-500 to-purple-500 rounded-xl flex items-center justify-center mr-4">
<i class="fas fa-comments text-white text-xl"></i>
</div>
<div>
<h1 class="text-2xl font-bold text-white">{{ room.name }}</h1>
{% if room.description %}
<p class="text-blue-200 mb-2">{{ room.description }}</p>
{% endif %}
</div>
</div>
<div class="flex flex-wrap gap-2">
{% if room.category %}
<span class="px-3 py-1 bg-blue-500/30 text-blue-200 rounded-full text-sm border border-blue-400/30">
<i class="fas fa-tag mr-1"></i>{{ room.category.title() }}
</span>
{% endif %}
<span class="message-time">{{ message.created_at.strftime('%H:%M') }}</span>
{% if room.related_post %}
<span class="px-3 py-1 bg-green-500/30 text-green-200 rounded-full text-sm border border-green-400/30">
<i class="fas fa-link mr-1"></i>Linked to Post
</span>
{% endif %}
<span class="px-3 py-1 bg-purple-500/30 text-purple-200 rounded-full text-sm border border-purple-400/30">
<i class="fas fa-users mr-1"></i>{{ room.participants.count() if room.participants else 0 }} Members
</span>
</div>
{% if room.related_post %}
<div class="mt-3 p-3 bg-white/5 rounded-lg border border-white/10">
<div class="flex items-center text-sm text-gray-300">
<i class="fas fa-newspaper mr-2 text-green-400"></i>
<span class="mr-2">Discussing:</span>
<a href="{{ url_for('community.post_detail', post_id=room.related_post.id) }}"
class="text-green-300 hover:text-green-200 underline transition-colors"
target="_blank">
{{ room.related_post.title }}
</a>
</div>
</div>
{% endif %}
<div class="message-content">
{{ message.content }}
{% if message.is_edited %}
<small class="text-muted"> (edited)</small>
{% endif %}
</div>
<div class="flex gap-2">
<button class="px-4 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg border border-white/20 transition-colors">
<i class="fas fa-users mr-1"></i> Members
</button>
<button class="px-4 py-2 bg-white/10 hover:bg-white/20 text-white rounded-lg border border-white/20 transition-colors">
<i class="fas fa-cog mr-1"></i> Settings
</button>
</div>
</div>
</div>
<!-- Chat Interface -->
<div class="bg-white/5 backdrop-blur-md rounded-2xl border border-white/20 overflow-hidden">
<!-- Messages Area -->
<div id="messages-container" class="h-96 overflow-y-auto p-6 space-y-4">
{% for message in messages %}
<div class="message mb-4 {{ 'ml-12' if message.sender_id == current_user.id else 'mr-12' }}">
<div class="flex {{ 'justify-end' if message.sender_id == current_user.id else 'justify-start' }}">
<div class="max-w-xs lg:max-w-md">
{% if not message.is_system_message %}
<div class="flex items-center mb-1 {{ 'justify-end' if message.sender_id == current_user.id else 'justify-start' }}">
<span class="text-xs text-gray-400">
{{ message.sender.nickname }}
{% if message.sender.is_admin %}
<span class="px-1 py-0.5 bg-yellow-100 text-yellow-800 text-xs rounded-full ml-1">ADMIN</span>
{% endif %}
• {{ message.created_at.strftime('%H:%M') }}
</span>
</div>
{% endif %}
<div class="rounded-2xl px-4 py-3 {{
'bg-gradient-to-r from-blue-600 to-purple-600 text-white' if message.sender_id == current_user.id else
'bg-yellow-100 border-yellow-300 text-yellow-800 text-center' if message.is_system_message else
'bg-white border border-gray-200 text-gray-800'
}}">
{% if message.is_system_message %}
<i class="fas fa-info-circle mr-2"></i>
{% endif %}
{{ message.content }}
{% if message.is_edited %}
<small class="opacity-75 text-xs block mt-1">(edited)</small>
{% endif %}
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="message-input-area">
<form id="messageForm" class="d-flex gap-2">
<input type="text"
id="messageInput"
class="form-control message-input"
placeholder="Type your message..."
maxlength="2000"
autocomplete="off">
<button type="submit" class="send-button">
<!-- Message Input -->
<div class="border-t border-white/20 p-4">
<form id="message-form" class="flex gap-3">
<div class="flex-1">
<input
type="text"
id="message-input"
class="w-full px-4 py-3 bg-white/10 border border-white/20 rounded-xl text-white placeholder-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
placeholder="Type your message..."
maxlength="1000"
autocomplete="off"
>
</div>
<button
type="submit"
class="px-6 py-3 bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white rounded-xl font-medium transition-all duration-200 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
>
<i class="fas fa-paper-plane"></i>
</button>
</form>
<small class="text-muted">Press Enter to send • Max 2000 characters</small>
</div>
</div>
<div class="participants-sidebar">
<h6 class="mb-3">Participants ({{ participants|length }})</h6>
{% for participant in participants %}
<div class="participant-item">
<div class="participant-avatar">
{{ participant.user.nickname[0].upper() }}
</div>
<div class="participant-info">
<div class="participant-name">{{ participant.user.nickname }}</div>
<div class="participant-role">
{{ participant.role.title() }}
{% if participant.user.is_admin %}• Admin{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
@@ -281,13 +136,13 @@ const currentUserId = {{ current_user.id }};
let lastMessageId = {{ messages[-1].id if messages else 0 }};
// Message form handling
document.getElementById('messageForm').addEventListener('submit', function(e) {
document.getElementById('message-form').addEventListener('submit', function(e) {
e.preventDefault();
sendMessage();
});
// Enter key handling
document.getElementById('messageInput').addEventListener('keypress', function(e) {
document.getElementById('message-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
@@ -295,7 +150,7 @@ document.getElementById('messageInput').addEventListener('keypress', function(e)
});
function sendMessage() {
const input = document.getElementById('messageInput');
const input = document.getElementById('message-input');
const content = input.value.trim();
if (!content) return;
@@ -306,8 +161,7 @@ function sendMessage() {
fetch(`/api/v1/chat/rooms/${roomId}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: content,
@@ -335,35 +189,44 @@ function sendMessage() {
}
function addMessageToUI(message) {
const messagesArea = document.getElementById('messagesArea');
const messagesArea = document.getElementById('messages-container');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${message.user.id === currentUserId ? 'own-message' : ''} ${message.message_type === 'system' ? 'system-message' : ''}`;
messageDiv.className = `message mb-4 ${message.user.id === currentUserId ? 'ml-12' : 'mr-12'}`;
messageDiv.setAttribute('data-message-id', message.id);
let messageHTML = '';
if (message.message_type !== 'system') {
messageHTML += `
<div class="message-header">
<span class="message-author">${message.user.nickname}</span>
${message.user.is_admin ? '<span class="admin-badge">ADMIN</span>' : ''}
<span class="message-time">${new Date(message.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}</span>
const isOwnMessage = message.user.id === currentUserId;
const isSystemMessage = message.message_type === 'system';
messageDiv.innerHTML = `
<div class="flex ${isOwnMessage ? 'justify-end' : 'justify-start'}">
<div class="max-w-xs lg:max-w-md">
${!isSystemMessage ? `
<div class="flex items-center mb-1 ${isOwnMessage ? 'justify-end' : 'justify-start'}">
<span class="text-xs text-gray-400">
${message.user.nickname} ${message.user.is_admin ? '<span class="px-1 py-0.5 bg-yellow-100 text-yellow-800 text-xs rounded-full ml-1">ADMIN</span>' : ''}${new Date(message.created_at).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'})}
</span>
</div>
` : ''}
<div class="rounded-2xl px-4 py-3 ${
isOwnMessage ? 'bg-gradient-to-r from-blue-600 to-purple-600 text-white' :
isSystemMessage ? 'bg-yellow-100 border-yellow-300 text-yellow-800 text-center' :
'bg-white border border-gray-200 text-gray-800'
}">
${isSystemMessage ? '<i class="fas fa-info-circle mr-2"></i>' : ''}
${message.content}
${message.is_edited ? '<small class="opacity-75 text-xs block mt-1"> (edited)</small>' : ''}
</div>
</div>
`;
}
messageHTML += `
<div class="message-content">
${message.content}
${message.is_edited ? '<small class="text-muted"> (edited)</small>' : ''}
</div>
`;
messageDiv.innerHTML = messageHTML;
messagesArea.appendChild(messageDiv);
lastMessageId = message.id;
}
function scrollToBottom() {
const messagesArea = document.getElementById('messagesArea');
const messagesArea = document.getElementById('messages-container');
messagesArea.scrollTop = messagesArea.scrollHeight;
}
@@ -390,11 +253,10 @@ scrollToBottom();
setInterval(loadNewMessages, 3000);
// Auto-focus on message input
document.getElementById('messageInput').focus();
document.getElementById('message-input').focus();
// Mobile app integration
if (window.ReactNativeWebView) {
// React Native WebView integration
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'chat_room_opened',
roomId: roomId,