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

414 lines
12 KiB
HTML

{% extends "base.html" %}
{% 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>
{% endif %}
<span class="message-time">{{ message.created_at.strftime('%H:%M') }}</span>
</div>
{% endif %}
<div class="message-content">
{{ message.content }}
{% if message.is_edited %}
<small class="text-muted"> (edited)</small>
{% endif %}
</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">
<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>
<script>
const roomId = {{ room.id }};
const currentUserId = {{ current_user.id }};
let lastMessageId = {{ messages[-1].id if messages else 0 }};
// Message form handling
document.getElementById('messageForm').addEventListener('submit', function(e) {
e.preventDefault();
sendMessage();
});
// Enter key handling
document.getElementById('messageInput').addEventListener('keypress', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
function sendMessage() {
const input = document.getElementById('messageInput');
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',
'X-CSRFToken': '{{ csrf_token() }}'
},
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('messagesArea');
const messageDiv = document.createElement('div');
messageDiv.className = `message ${message.user.id === currentUserId ? 'own-message' : ''} ${message.message_type === 'system' ? 'system-message' : ''}`;
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>
</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');
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('messageInput').focus();
// Mobile app integration
if (window.ReactNativeWebView) {
// React Native WebView integration
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 %}