Added profile pictures

Added profile pictures and fixed some bugs
This commit is contained in:
Olai Vike Bøe 2024-06-24 23:06:46 +02:00
parent 44c2b2cffa
commit 0ac1f2795b
11 changed files with 367 additions and 99 deletions

72
app.py
View file

@ -1,8 +1,12 @@
from PIL import Image # Import the Image module from Pillow
from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response from flask import Flask, render_template, request, redirect, url_for, session, jsonify, send_from_directory, make_response
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from cryptography.fernet import Fernet, InvalidToken from cryptography.fernet import Fernet, InvalidToken
import os import os
import random
import string
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from werkzeug.utils import secure_filename # Add this line
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from flask_socketio import SocketIO, emit, join_room, leave_room from flask_socketio import SocketIO, emit, join_room, leave_room
import logging import logging
@ -62,6 +66,7 @@ class User(db.Model):
online = db.Column(db.Boolean, nullable=False, default=False) online = db.Column(db.Boolean, nullable=False, default=False)
is_admin = db.Column(db.Boolean, nullable=False, default=False) is_admin = db.Column(db.Boolean, nullable=False, default=False)
disabled = db.Column(db.Boolean, nullable=False, default=False) disabled = db.Column(db.Boolean, nullable=False, default=False)
profile_picture = db.Column(db.String(150), nullable=True)
class Message(db.Model): class Message(db.Model):
id = db.Column(db.Integer, primary_key=True) id = db.Column(db.Integer, primary_key=True)
@ -310,6 +315,7 @@ def chat(friend_id):
friend_user = User.query.filter_by(id=friend_id).first() friend_user = User.query.filter_by(id=friend_id).first()
user = User.query.filter_by(id=current_user_id).first() user = User.query.filter_by(id=current_user_id).first()
friends = Friend.query.filter_by(user_id=user.id).all() friends = Friend.query.filter_by(user_id=user.id).all()
profile_picture = user.profile_picture # Assuming this is the path to the profile picture
friend_ids = [f.friend_id for f in friends] friend_ids = [f.friend_id for f in friends]
friend_users = User.query.filter(User.id.in_(friend_ids)).all() friend_users = User.query.filter(User.id.in_(friend_ids)).all()
@ -321,7 +327,7 @@ def chat(friend_id):
groups = Group.query.filter(Group.id.in_(group_ids)).all() groups = Group.query.filter(Group.id.in_(group_ids)).all()
logger.info(f"User {session['username']} opened chat with {friend_user.username}") logger.info(f"User {session['username']} opened chat with {friend_user.username}")
return render_template('chat.html', username=session['username'], friend_id=friend_id, friend_username=friend_user.username, friends=friend_users, friend_requests=friend_requests, groups=groups, is_admin=user.is_admin) return render_template('chat.html', friend=friend, profile_picture=profile_picture, username=session['username'], friend_id=friend_id, friend_username=friend_user.username, friends=friend_users, friend_requests=friend_requests, groups=groups, is_admin=user.is_admin)
else: else:
logger.warning(f"User {session['username']} attempted to access chat with non-friend user_id: {friend_id}") logger.warning(f"User {session['username']} attempted to access chat with non-friend user_id: {friend_id}")
return redirect(url_for('dashboard')) return redirect(url_for('dashboard'))
@ -343,11 +349,9 @@ def send_message(receiver):
content_type = 'file' content_type = 'file'
if content: if content:
# Encrypt text content only, do not encrypt file names
encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content encrypted_content = cipher.encrypt(content.encode()).decode() if content_type == 'text' else content
timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc) timestamp_dt = datetime.strptime(timestamp, '%Y-%m-%d %H:%M:%S').replace(tzinfo=timezone.utc)
# Check if they are friends
sender_user = User.query.filter_by(username=session['username']).first() sender_user = User.query.filter_by(username=session['username']).first()
receiver_user = User.query.filter_by(id=receiver).first() receiver_user = User.query.filter_by(id=receiver).first()
if not receiver_user: if not receiver_user:
@ -362,20 +366,22 @@ def send_message(receiver):
decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content decrypted_content = cipher.decrypt(encrypted_content.encode()).decode() if content_type == 'text' else encrypted_content
socketio.emit('new_message', { socketio.emit('new_message', {
'sender': session['username'], 'sender': session['username'],
'receiver': receiver_user.username, # Include the receiver information
'content': decrypted_content, 'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Ensure this is correct
'content_type': content_type, 'content_type': content_type,
'timestamp': timestamp, 'timestamp': timestamp,
'id': new_message.id 'id': new_message.id
}, room=receiver_user.username) }, room=receiver_user.username)
logger.info(f"Message sent from {session['username']} to {receiver_user.username}") logger.info(f"Message sent from {session['username']} to {receiver_user.username}")
return jsonify({'status': 'Message sent', 'message_id': new_message.id}), 200 return jsonify({'status': 'Message sent', 'message_id': new_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200
else: else:
pending_message = PendingMessage(sender=session['username'], receiver=receiver_user.username, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt) pending_message = PendingMessage(sender=session['username'], receiver=receiver_user.username, content=encrypted_content, content_type=content_type, timestamp=timestamp_dt)
db.session.add(pending_message) db.session.add(pending_message)
db.session.commit() db.session.commit()
logger.info(f"Pending message from {session['username']} to {receiver_user.username}") logger.info(f"Pending message from {session['username']} to {receiver_user.username}")
return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id}), 200 return jsonify({'status': 'Pending message sent', 'message_id': pending_message.id, 'sender_profile_picture': sender_user.profile_picture}), 200
return jsonify({'error': 'No content or file provided'}), 400 return jsonify({'error': 'No content or file provided'}), 400
except Exception as e: except Exception as e:
logger.error(f"Error sending message from {session['username']} to {receiver}: {e}") logger.error(f"Error sending message from {session['username']} to {receiver}: {e}")
@ -399,6 +405,7 @@ def get_messages(friend_id):
{ {
'id': msg.id, 'id': msg.id,
'sender': msg.sender.username, 'sender': msg.sender.username,
'sender_profile_picture': f'/cdn/{msg.sender.profile_picture}' if msg.sender.profile_picture else None,
'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content, 'content': cipher.decrypt(msg.content.encode()).decode() if msg.content_type == 'text' else msg.content,
'content_type': msg.content_type, 'content_type': msg.content_type,
'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S") 'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S")
@ -410,6 +417,44 @@ def get_messages(friend_id):
logger.warning(f"Unauthorized message retrieval attempt by user {session.get('username', 'unknown')}") logger.warning(f"Unauthorized message retrieval attempt by user {session.get('username', 'unknown')}")
return jsonify({'error': 'Unauthorized'}), 401 return jsonify({'error': 'Unauthorized'}), 401
def generate_random_string(length=10):
characters = string.ascii_letters + string.digits
return ''.join(random.choice(characters) for i in range(length))
def allowed_file(filename):
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'}
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def compress_image(image_path, quality=15):
with Image.open(image_path) as img:
if img.format == 'PNG':
img.save(image_path, "PNG", optimize=True)
else:
img = img.convert("RGB") # Ensure the image is in RGB mode for non-PNG files
img.save(image_path, "JPEG", quality=quality) # Save with JPEG format and compression
@app.route('/upload_profile_picture', methods=['POST'])
def upload_profile_picture():
if 'username' in session:
user = User.query.filter_by(username=session['username']).first()
if 'profile_picture' in request.files:
file = request.files['profile_picture']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
random_string = generate_random_string()
filename_parts = filename.rsplit('.', 1)
filename = f"{filename_parts[0]}-{random_string}.{filename_parts[1]}"
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
file.save(file_path)
# Compress the image
compress_image(file_path, quality=85)
user.profile_picture = filename
db.session.commit()
logger.info(f"User {session['username']} uploaded a new profile picture: {filename}")
return redirect(url_for('dashboard')) # Redirect to a valid endpoint
return redirect(url_for('dashboard')) # Redirect to a valid endpoint if not logged in
@app.route('/cdn/<filename>') @app.route('/cdn/<filename>')
def uploaded_file(filename): def uploaded_file(filename):
@ -514,6 +559,15 @@ def remove_friend(friend_id):
reciprocal_friend = Friend.query.filter_by(user_id=friend.friend_id, friend_id=user.id).first() reciprocal_friend = Friend.query.filter_by(user_id=friend.friend_id, friend_id=user.id).first()
if reciprocal_friend: if reciprocal_friend:
db.session.delete(reciprocal_friend) db.session.delete(reciprocal_friend)
# Remove friend requests associated with the friend
sent_request = FriendRequest.query.filter_by(sender_id=user.id, receiver_id=friend_id).first()
if sent_request:
db.session.delete(sent_request)
received_request = FriendRequest.query.filter_by(sender_id=friend_id, receiver_id=user.id).first()
if received_request:
db.session.delete(received_request)
db.session.commit() db.session.commit()
socketio.emit('friend_removed', { socketio.emit('friend_removed', {
@ -522,10 +576,10 @@ def remove_friend(friend_id):
}, room=friend.friend.username) }, room=friend.friend.username)
logger.info(f"Friend {friend.friend.username} removed by {session['username']}") logger.info(f"Friend {friend.friend.username} removed by {session['username']}")
return redirect(url_for('dashboard')) return jsonify({'status': 'success'}), 200
logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}") logger.warning(f"Friend removal failed: Friend not found or unauthorized access by {session['username']}")
return 'Friend not found' return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404
return redirect(url_for('login')) return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401
@app.route('/create_group', methods=['POST']) @app.route('/create_group', methods=['POST'])
def create_group(): def create_group():
@ -633,6 +687,7 @@ def send_group_message(group_id):
'group_id': group_id, 'group_id': group_id,
'sender': session['username'], 'sender': session['username'],
'content': decrypted_content, 'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Add profile picture
'content_type': content_type, 'content_type': content_type,
'timestamp': timestamp, 'timestamp': timestamp,
'id': new_message.id 'id': new_message.id
@ -656,6 +711,7 @@ def get_group_messages(group_id):
'id': msg.GroupMessage.id, 'id': msg.GroupMessage.id,
'group_id': msg.GroupMessage.group_id, 'group_id': msg.GroupMessage.group_id,
'sender': msg.User.username, # Use the username from the User table 'sender': msg.User.username, # Use the username from the User table
'sender_profile_picture': msg.User.profile_picture, # Add profile picture
'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content, 'content': cipher.decrypt(msg.GroupMessage.content.encode()).decode() if msg.GroupMessage.content_type == 'text' else msg.GroupMessage.content,
'content_type': msg.GroupMessage.content_type, 'content_type': msg.GroupMessage.content_type,
'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S") 'timestamp': msg.GroupMessage.timestamp.strftime("%Y-%m-%d %H:%M:%S")

View file

@ -7,3 +7,4 @@ Werkzeug==3.0.3
python-socketio==5.11.2 python-socketio==5.11.2
python-engineio==4.9.1 python-engineio==4.9.1
PyJWT==2.8.0 PyJWT==2.8.0
Pillow==10.3.0

View file

@ -111,3 +111,14 @@
.dropdown-content { .dropdown-content {
@apply bg-[#fafafa] dark:!bg-[#141414] shadow text-black dark:!text-white @apply bg-[#fafafa] dark:!bg-[#141414] shadow text-black dark:!text-white
} }
[data-message-text="---"]{
width:auto !important;
padding: 0 !important;
}
hr{
height: 1px;
border: none;
@apply !bg-[#f1f1f1] dark:!bg-[#363636]
}

View file

@ -68,15 +68,62 @@ document.addEventListener('DOMContentLoaded', (event) => {
return formattedTime; return formattedTime;
} }
function isImageUrl(url) { function isImageUrl(url) {
return /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(url); return /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(url);
} }
function appendMessage(msg, showUsername, senderProfilePicture) {
const currentTimestamp = new Date(msg.timestamp).getTime();
previousSender = msg.sender;
previousTimestamp = currentTimestamp;
const newMessage = document.createElement('div');
newMessage.classList.add('message');
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
if (msg.sender === username) {
usernameElement.classList.add('message-myself-sender');
}
usernameElement.innerHTML = `
<img src="/cdn/${senderProfilePicture}" alt="/cdn/${msg.sender}" class="profile-picture">
<strong>${msg.sender}</strong>
<div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement);
}
if (msg.content_type === 'text') {
if (isImageUrl(msg.content)) {
newMessage.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
newMessage.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
} else if (msg.content_type === 'file') {
const isImage = isImageUrl(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
if (isImage) {
newMessage.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else if (isVideo) {
newMessage.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
newMessage.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
}
let previousSender = null; let previousSender = null;
let previousTimestamp = 0; let previousTimestamp = 0;
socket.on('new_message', function(data) { socket.on('new_message', function(data) {
// Check if the message is for the current chat
if (friendUsername !== data.sender && friendId != data.sender_id) {
return;
}
showNotification('New Message', `You have received a new message from ${data.sender}`); showNotification('New Message', `You have received a new message from ${data.sender}`);
const currentTimestamp = new Date(data.timestamp).getTime(); const currentTimestamp = new Date(data.timestamp).getTime();
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp); const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
@ -89,14 +136,18 @@ document.addEventListener('DOMContentLoaded', (event) => {
const newMessage = document.createElement('div'); const newMessage = document.createElement('div');
newMessage.classList.add('message'); newMessage.classList.add('message');
newMessage.dataset.messageId = data.id; // Ensure this is correct newMessage.dataset.messageId = data.id;
newMessage.dataset.messageText = data.content; newMessage.dataset.messageText = data.content;
if (showUsername) { if (showUsername) {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
const timestampFormatted = formatLocalTime(data.timestamp); const timestampFormatted = formatLocalTime(data.timestamp);
usernameElement.innerHTML = `<strong>${data.sender}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`;
// Include the profile picture if available
const profilePicture = data.sender_profile_picture ? `<img src="/cdn/${data.sender_profile_picture}" alt="${data.sender}" class="profile-picture enhanceable-image">` : '';
usernameElement.innerHTML = `${profilePicture} <strong>${data.sender}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -122,6 +173,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
scrollToBottom(); scrollToBottom();
}); });
if (friendId) { if (friendId) {
fetch(`/get_messages/${friendId}`) fetch(`/get_messages/${friendId}`)
.then(response => response.json()) .then(response => response.json())
@ -139,16 +191,15 @@ document.addEventListener('DOMContentLoaded', (event) => {
if (showUsername) { if (showUsername) {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
if (msg.sender === username) {
const timestampFormatted = new Date(msg.timestamp).toLocaleString(undefined, { usernameElement.classList.add('message-myself-sender');
weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', }
hour: '2-digit', minute: '2-digit' // Include the profile picture if available
}); const profilePicture = msg.sender_profile_picture ? `<img src="${msg.sender_profile_picture}" alt="${msg.sender}" class="profile-picture enhanceable-image">` : '';
usernameElement.innerHTML = `<strong>${msg.sender}</strong> <div class="message-sender-timestamp">${timestampFormatted}</div>`; usernameElement.innerHTML = `${profilePicture} <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
const messageElement = document.createElement('div'); const messageElement = document.createElement('div');
messageElement.classList.add('message'); messageElement.classList.add('message');
@ -156,16 +207,20 @@ document.addEventListener('DOMContentLoaded', (event) => {
messageElement.dataset.messageText = msg.content; messageElement.dataset.messageText = msg.content;
if (msg.content_type === 'text') { if (msg.content_type === 'text') {
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${msg.timestamp}</div>`; if (isImageUrl(msg.content)) {
messageElement.innerHTML = `<img src="${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else {
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
} else if (msg.content_type === 'file') { } else if (msg.content_type === 'file') {
const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(msg.content); const isImage = isImageUrl(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content); const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
if (isImage) { if (isImage) {
messageElement.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${msg.timestamp}</div>`; messageElement.innerHTML = `<img src="/cdn/${msg.content}" alt="Image" class="enhanceable-image" style="max-width: 200px; max-height: 200px;" /><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else if (isVideo) { } else if (isVideo) {
messageElement.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${msg.timestamp}</div>`; messageElement.innerHTML = `<video controls style="max-width: 200px; max-height: 200px;"><source src="/cdn/${msg.content}" type="video/mp4"></video><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} else { } else {
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${msg.timestamp}</div>`; messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
} }
} }
@ -176,6 +231,8 @@ document.addEventListener('DOMContentLoaded', (event) => {
}) })
.catch(error => console.error('Error fetching messages:', error)); .catch(error => console.error('Error fetching messages:', error));
} }
const sendMessageForm = document.querySelector('.send-message-form'); const sendMessageForm = document.querySelector('.send-message-form');
if (sendMessageForm) { if (sendMessageForm) {
sendMessageForm.addEventListener('submit', function(event) { sendMessageForm.addEventListener('submit', function(event) {
@ -203,10 +260,13 @@ document.addEventListener('DOMContentLoaded', (event) => {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
const timestampFormatted = formatLocalTime(timestamp); const timestampFormatted = formatLocalTime(timestamp);
usernameElement.innerHTML = `<strong>${username}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`; const profilePictureUrl = `/cdn/${profilePicture}`; // Correct reference
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${username}" class="profile-picture enhanceable-image"> <strong>${username}</strong>: <div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
if (content) { if (content) {
newMessage.dataset.messageText = content; newMessage.dataset.messageText = content;
if (isImageUrl(content)) { if (isImageUrl(content)) {

View file

@ -75,7 +75,8 @@ document.addEventListener('DOMContentLoaded', (event) => {
if (showUsername) { if (showUsername) {
const usernameElement = document.createElement('div'); const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender'); usernameElement.classList.add('message-sender');
usernameElement.innerHTML = `<strong>${data.sender}</strong>:<div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`; const profilePictureUrl = `/cdn/${data.sender_profile_picture}`;
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${data.sender}" class="profile-picture"> <strong>${data.sender}</strong>:<div class="message-sender-timestamp">${formatLocalTime(data.timestamp)}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
@ -126,11 +127,13 @@ document.addEventListener('DOMContentLoaded', (event) => {
if (msg.sender === username) { if (msg.sender === username) {
usernameElement.classList.add('message-myself-sender'); usernameElement.classList.add('message-myself-sender');
} }
usernameElement.innerHTML = `<strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`; const profilePictureUrl = `/cdn/${msg.sender_profile_picture}`;
usernameElement.innerHTML = `<img src="${profilePictureUrl}" alt="${msg.sender}" class="profile-picture"> <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement); messagesList.appendChild(usernameElement);
} }
const messageElement = document.createElement('div'); const messageElement = document.createElement('div');
messageElement.classList.add('message'); messageElement.classList.add('message');

View file

@ -624,6 +624,10 @@ video {
margin-top: 0.75rem; margin-top: 0.75rem;
} }
.block {
display: block;
}
.flex { .flex {
display: flex; display: flex;
} }
@ -636,6 +640,10 @@ video {
height: 400px !important; height: 400px !important;
} }
.\!h-\[465px\] {
height: 465px !important;
}
.\!h-fit { .\!h-fit {
height: -moz-fit-content !important; height: -moz-fit-content !important;
height: fit-content !important; height: fit-content !important;
@ -1178,6 +1186,23 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity)) !important; color: rgb(255 255 255 / var(--tw-text-opacity)) !important;
} }
[data-message-text="---"]{
width:auto !important;
padding: 0 !important;
}
hr{
height: 1px;
border: none;
--tw-bg-opacity: 1 !important;
background-color: rgb(241 241 241 / var(--tw-bg-opacity)) !important
}
hr:is(.dark *) {
--tw-bg-opacity: 1 !important;
background-color: rgb(54 54 54 / var(--tw-bg-opacity)) !important;
}
.hover\:\!text-slate-400:hover { .hover\:\!text-slate-400:hover {
--tw-text-opacity: 1 !important; --tw-text-opacity: 1 !important;
color: rgb(148 163 184 / var(--tw-text-opacity)) !important; color: rgb(148 163 184 / var(--tw-text-opacity)) !important;
@ -1213,6 +1238,11 @@ video {
background-color: rgb(69 71 146 / var(--tw-bg-opacity)) !important; background-color: rgb(69 71 146 / var(--tw-bg-opacity)) !important;
} }
.dark\:\!bg-black:is(.dark *) {
--tw-bg-opacity: 1 !important;
background-color: rgb(0 0 0 / var(--tw-bg-opacity)) !important;
}
.dark\:\!bg-neutral-800:is(.dark *) { .dark\:\!bg-neutral-800:is(.dark *) {
--tw-bg-opacity: 1 !important; --tw-bg-opacity: 1 !important;
background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important; background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;

View file

@ -431,3 +431,19 @@ ul#friends-list {
/* Change the background color of the dropdown button when the dropdown content is shown */ /* Change the background color of the dropdown button when the dropdown content is shown */
.dropdown:hover .dropbtn {background-color: transparent;} .dropdown:hover .dropbtn {background-color: transparent;}
.profile-picture {
width: 40px !important;
height: 40px !important;
min-width: 40px !important;
min-height: 40px !important;
border-radius: 50% !important;
margin-right: 10px;
position: relative;
right: 44px;
top: 14px;
}
.message-sender > strong, .message-sender > .message-sender-timestamp {
position: relative;
right: 48px;
}

View file

@ -3,6 +3,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Admin Panel</title> <title>Admin Panel</title>
{% include 'head.html' %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script> <script>
function changePassword(userId) { function changePassword(userId) {
@ -62,10 +63,10 @@
} }
</script> </script>
</head> </head>
<body> <body class="dark:!bg-black bg-white">
<div class="container"> <div class="container">
<h1>Admin Panel</h1> <h1 class="dark:!text-white text-black">Admin Panel</h1>
<table class="table"> <table class="table dark:!text-white text-black">
<thead> <thead>
<tr> <tr>
<th>Username</th> <th>Username</th>

View file

@ -8,6 +8,7 @@
var friendId = "{{ friend_id }}"; var friendId = "{{ friend_id }}";
var friendUsername = "{{ friend_username }}"; var friendUsername = "{{ friend_username }}";
var username = "{{ username }}"; var username = "{{ username }}";
var profilePicture = "{{ profile_picture }}"; // Add this line
</script> </script>
<script src="{{ url_for('static', filename='js/chat.js') }}"></script> <script src="{{ url_for('static', filename='js/chat.js') }}"></script>
</head> </head>

View file

@ -6,7 +6,7 @@
{% include 'head.html' %} {% include 'head.html' %}
</head> </head>
<body class="login-body"> <body class="login-body">
<div class="login-div"> <div class="login-div !h-[465px]">
<form class="login-form" id="settings-form"> <form class="login-form" id="settings-form">
<img class="login-img" src="{{ url_for('static', filename='images/ms-style-logo.svg') }}" alt="Image"> <img class="login-img" src="{{ url_for('static', filename='images/ms-style-logo.svg') }}" alt="Image">
<h2 class="login-h2">Settings</h2> <h2 class="login-h2">Settings</h2>
@ -18,13 +18,24 @@
<option value="dark" {% if current_theme == 'dark' %}selected{% endif %}>Dark</option> <option value="dark" {% if current_theme == 'dark' %}selected{% endif %}>Dark</option>
</select> </select>
</div> </div>
<div class="login-input-div-first !flex-row">
<a class="no-account no-account-button" href="{{ url_for('dashboard') }}"> Go Back</a>
</div>
<div class="login-input-div-first items-end"> <div class="login-input-div-first items-end">
<button class="ms-button-signin" type="submit" class="btn btn-primary">Save Settings</button> <button class="ms-button-signin" type="submit" class="btn btn-primary">Save Settings</button>
</div> </div>
</form> </form>
<form class="login-form" id="upload-profile-picture-form" action="{{ url_for('upload_profile_picture') }}" method="POST" enctype="multipart/form-data">
<h2 class="login-h2">Upload Profile Picture</h2>
<div class="login-input-div-first">
<label for="profile_picture" class="login-label">Choose a profile picture:</label>
<input class="login-input" type="file" name="profile_picture" id="profile_picture" accept=".png, .jpg, .jpeg, .gif, .webp">
</div>
<div class="login-input-div-first items-end">
<button class="ms-button-signin" type="submit" class="btn btn-primary">Upload</button>
</div>
<div class="login-input-div-first !flex-row">
<a class="no-account no-account-button" href="{{ url_for('dashboard') }}"> Go Back</a>
</div>
</form>
<div id="message" class="mt-3"></div> <div id="message" class="mt-3"></div>
</div> </div>
<script> <script>
@ -62,6 +73,12 @@
const messageDiv = document.getElementById('message'); const messageDiv = document.getElementById('message');
messageDiv.innerHTML = '<div class="alert alert-success">Settings saved successfully!</div>'; messageDiv.innerHTML = '<div class="alert alert-success">Settings saved successfully!</div>';
}); });
// Handle the profile picture upload
document.querySelector('#upload-profile-picture-form').addEventListener('submit', function(event) {
const messageDiv = document.getElementById('message');
messageDiv.innerHTML = ''; // Clear any previous messages
});
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,8 +1,5 @@
<div class="sidebar w-fit bg-transparent text-white flex flex-col items-center"> <div class="sidebar w-fit bg-transparent text-white flex flex-col items-center">
<div class="sidebar-content h-full w-60 p-5 rounded-t-lg"> <div class="sidebar-content h-full w-60 p-5 rounded-t-lg">
<h4 class="flex flex-row items-center"> <h4 class="flex flex-row items-center">
<i class="fa-solid fa-table-columns text-slate-900 dark:!text-slate-200"></i> <i class="fa-solid fa-table-columns text-slate-900 dark:!text-slate-200"></i>
<a class="text-slate-900 dark:!text-slate-100 pl-2" href="/dashboard">Dashboard</a> <a class="text-slate-900 dark:!text-slate-100 pl-2" href="/dashboard">Dashboard</a>
@ -13,7 +10,7 @@
</h4> </h4>
<ul id="friends-list"> <ul id="friends-list">
{% for friend in friends %} {% for friend in friends %}
<li data-username="{{ friend.username }}"> <li data-id="{{ friend.id }}" data-username="{{ friend.username }}">
<span class="status-indicator {{ 'online' if friend.online else 'offline' }}"></span> <span class="status-indicator {{ 'online' if friend.online else 'offline' }}"></span>
<a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a> <a class="dark:!text-slate-400 text-slate-900 hover:!text-slate-400 hover:dark:!text-slate-200" href="/chat/{{ friend.id }}">{{ friend.username }}</a>
</li> </li>
@ -33,12 +30,87 @@
</div> </div>
</div> </div>
<div id="context-menu2" class="custom-context-menu">
<ul>
<li id="copy-uid">Copy User ID</li>
<li id="rm-friend">Remove Friend</li>
</ul>
</div>
<div id="message" class="mt-3"></div>
<script> <script>
// Function to refresh the page document.addEventListener('DOMContentLoaded', function() {
function refreshPage() { const contextMenu = document.getElementById('context-menu2');
location.reload(); const friendsList = document.getElementById('friends-list');
let selectedFriend = null;
if (friendsList) {
friendsList.addEventListener('contextmenu', function(event) {
event.preventDefault();
const friendElement = event.target.closest('li');
if (friendElement) {
selectedFriend = friendElement;
if (contextMenu) {
contextMenu.style.top = `${event.clientY}px`;
contextMenu.style.left = `${event.clientX}px`;
contextMenu.style.display = 'block';
}
}
});
} }
// Set the interval to refresh the page every 3 seconds (3000 milliseconds) document.addEventListener('click', function() {
// setInterval(refreshPage, 2000); if (contextMenu) {
contextMenu.style.display = 'none';
}
});
const copyUidButton = document.getElementById('copy-uid');
const removeFriendButton = document.getElementById('rm-friend');
if (copyUidButton) {
copyUidButton.addEventListener('click', function() {
if (selectedFriend) {
const friendId = selectedFriend.getAttribute('data-id');
navigator.clipboard.writeText(friendId).then(() => {
alert('User ID copied to clipboard');
});
}
});
}
if (removeFriendButton) {
removeFriendButton.addEventListener('click', function() {
if (selectedFriend) {
const friendId = selectedFriend.getAttribute('data-id');
fetch(`/remove_friend/${friendId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
}).then(response => response.json())
.then(data => {
const messageDiv = document.getElementById('message'); // Add a div with id 'message' to display messages
if (data.status === 'success') {
selectedFriend.remove();
if (messageDiv) {
messageDiv.innerHTML = '<div class="alert alert-success">Friend removed successfully</div>';
}
} else {
if (messageDiv) {
messageDiv.innerHTML = `<div class="alert alert-danger">Error removing friend: ${data.message}</div>`;
}
}
}).catch(error => {
console.error('Error removing friend:', error);
const messageDiv = document.getElementById('message');
if (messageDiv) {
messageDiv.innerHTML = '<div class="alert alert-danger">Error removing friend</div>';
}
});
}
});
}
});
</script> </script>