- 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
414 lines
12 KiB
HTML
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 %}
|