Files
moto-adv-website/app/templates/chat/room.html
ske087 30bd4c62ad 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.
2025-08-10 00:22:33 +03:00

276 lines
12 KiB
HTML

{% extends "base.html" %}
{% block title %}{{ room.name }} - Chat{% endblock %}
{% block content %}
<!-- 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 %}
{% 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>
<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>
<!-- 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>
</div>
</div>
</div>
</div>
<script>
const roomId = {{ room.id }};
const currentUserId = {{ current_user.id }};
let lastMessageId = {{ messages[-1].id if messages else 0 }};
// Message form handling
document.getElementById('message-form').addEventListener('submit', function(e) {
e.preventDefault();
sendMessage();
});
// Enter key handling
document.getElementById('message-input').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
function sendMessage() {
const input = document.getElementById('message-input');
const content = input.value.trim();
if (!content) return;
// Disable input while sending
input.disabled = true;
fetch(`/api/v1/chat/rooms/${roomId}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
content: content,
message_type: 'text'
})
})
.then(response => response.json())
.then(data => {
if (data.success) {
input.value = '';
addMessageToUI(data.message);
scrollToBottom();
} else {
alert('Error sending message: ' + data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('Failed to send message');
})
.finally(() => {
input.disabled = false;
input.focus();
});
}
function addMessageToUI(message) {
const messagesArea = document.getElementById('messages-container');
const messageDiv = document.createElement('div');
messageDiv.className = `message mb-4 ${message.user.id === currentUserId ? 'ml-12' : 'mr-12'}`;
messageDiv.setAttribute('data-message-id', message.id);
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>
</div>
`;
messagesArea.appendChild(messageDiv);
lastMessageId = message.id;
}
function scrollToBottom() {
const messagesArea = document.getElementById('messages-container');
messagesArea.scrollTop = messagesArea.scrollHeight;
}
function loadNewMessages() {
fetch(`/api/v1/chat/rooms/${roomId}/messages?after=${lastMessageId}`)
.then(response => response.json())
.then(data => {
if (data.success && data.messages.length > 0) {
data.messages.forEach(message => {
addMessageToUI(message);
});
scrollToBottom();
}
})
.catch(error => {
console.error('Error loading new messages:', error);
});
}
// Auto-scroll to bottom on load
scrollToBottom();
// Poll for new messages every 3 seconds
setInterval(loadNewMessages, 3000);
// Auto-focus on message input
document.getElementById('message-input').focus();
// Mobile app integration
if (window.ReactNativeWebView) {
window.ReactNativeWebView.postMessage(JSON.stringify({
type: 'chat_room_opened',
roomId: roomId,
roomName: '{{ room.name }}'
}));
}
// Flutter WebView integration
if (window.flutter_inappwebview) {
window.flutter_inappwebview.callHandler('chatRoomOpened', {
roomId: roomId,
roomName: '{{ room.name }}'
});
}
</script>
{% endblock %}