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_sqlalchemy import SQLAlchemy
from cryptography.fernet import Fernet, InvalidToken
import os
import random
import string
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 flask_socketio import SocketIO, emit, join_room, leave_room
import logging
@ -62,6 +66,7 @@ class User(db.Model):
online = 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)
profile_picture = db.Column(db.String(150), nullable=True)
class Message(db.Model):
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()
user = User.query.filter_by(id=current_user_id).first()
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_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()
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:
logger.warning(f"User {session['username']} attempted to access chat with non-friend user_id: {friend_id}")
return redirect(url_for('dashboard'))
@ -343,11 +349,9 @@ def send_message(receiver):
content_type = 'file'
if content:
# Encrypt text content only, do not encrypt file names
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)
# Check if they are friends
sender_user = User.query.filter_by(username=session['username']).first()
receiver_user = User.query.filter_by(id=receiver).first()
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
socketio.emit('new_message', {
'sender': session['username'],
'receiver': receiver_user.username, # Include the receiver information
'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Ensure this is correct
'content_type': content_type,
'timestamp': timestamp,
'id': new_message.id
}, room=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:
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.commit()
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
except Exception as 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,
'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_type': msg.content_type,
'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')}")
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>')
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()
if 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()
socketio.emit('friend_removed', {
@ -522,10 +576,10 @@ def remove_friend(friend_id):
}, room=friend.friend.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']}")
return 'Friend not found'
return redirect(url_for('login'))
return jsonify({'status': 'error', 'message': 'Friend not found or unauthorized access'}), 404
return jsonify({'status': 'error', 'message': 'Unauthorized'}), 401
@app.route('/create_group', methods=['POST'])
def create_group():
@ -633,6 +687,7 @@ def send_group_message(group_id):
'group_id': group_id,
'sender': session['username'],
'content': decrypted_content,
'sender_profile_picture': sender_user.profile_picture, # Add profile picture
'content_type': content_type,
'timestamp': timestamp,
'id': new_message.id
@ -656,6 +711,7 @@ def get_group_messages(group_id):
'id': msg.GroupMessage.id,
'group_id': msg.GroupMessage.group_id,
'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_type': msg.GroupMessage.content_type,
'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-engineio==4.9.1
PyJWT==2.8.0
Pillow==10.3.0

View file

@ -110,4 +110,15 @@
.dropdown-content {
@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

@ -49,14 +49,14 @@ document.addEventListener('DOMContentLoaded', (event) => {
function formatLocalTime(utcTimestamp) {
const date = new Date(utcTimestamp);
const localDate = new Date(date.getTime() - date.getTimezoneOffset() * 60000);
const now = new Date();
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
const timeFormatter = { hour: '2-digit', minute: '2-digit' };
const dateFormatter = { year: 'numeric', month: '2-digit', day: '2-digit' };
let formattedTime;
if (localDate >= today) {
formattedTime = `Today at ${localDate.toLocaleTimeString(undefined, timeFormatter)}`;
@ -67,39 +67,90 @@ document.addEventListener('DOMContentLoaded', (event) => {
}
return formattedTime;
}
function isImageUrl(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 previousTimestamp = 0;
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}`);
const currentTimestamp = new Date(data.timestamp).getTime();
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = data.sender;
previousTimestamp = currentTimestamp;
const messageElement = document.createElement('div');
messageElement.classList.add('message');
const newMessage = document.createElement('div');
newMessage.classList.add('message');
newMessage.dataset.messageId = data.id; // Ensure this is correct
newMessage.dataset.messageText = data.content;
newMessage.dataset.messageId = data.id;
newMessage.dataset.messageText = data.content;
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
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);
}
if (data.content_type === 'text') {
if (isImageUrl(data.content)) {
newMessage.innerHTML = `<img src="${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
@ -117,65 +168,71 @@ document.addEventListener('DOMContentLoaded', (event) => {
newMessage.innerHTML = `<a href="/cdn/${data.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
});
if (friendId) {
fetch(`/get_messages/${friendId}`)
.then(response => response.json())
.then(data => {
messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `Chatting with ${friendUsername}`;
document.getElementById('send-message-form').dataset.receiver = friendId;
data.messages.forEach((msg, index, messages) => {
const currentTimestamp = new Date(msg.timestamp).getTime();
const previousMessage = messages[index - 1];
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
const timestampFormatted = new Date(msg.timestamp).toLocaleString(undefined, {
weekday: 'short', year: 'numeric', month: 'short', day: 'numeric',
hour: '2-digit', minute: '2-digit'
});
usernameElement.innerHTML = `<strong>${msg.sender}</strong> <div class="message-sender-timestamp">${timestampFormatted}</div>`;
messagesList.appendChild(usernameElement);
.then(response => response.json())
.then(data => {
messagesList.innerHTML = '';
document.getElementById('chat-with').textContent = `Chatting with ${friendUsername}`;
document.getElementById('send-message-form').dataset.receiver = friendId;
data.messages.forEach((msg, index, messages) => {
const currentTimestamp = new Date(msg.timestamp).getTime();
const previousMessage = messages[index - 1];
const previousTimestamp = previousMessage ? new Date(previousMessage.timestamp).getTime() : 0;
const showUsername = index === 0 || msg.sender !== previousMessage.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
if (showUsername) {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
if (msg.sender === username) {
usernameElement.classList.add('message-myself-sender');
}
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.dataset.messageId = msg.id;
messageElement.dataset.messageText = msg.content;
if (msg.content_type === 'text') {
messageElement.innerHTML = `${md.render(msg.content)}<div class="timestamp">${msg.timestamp}</div>`;
} else if (msg.content_type === 'file') {
const isImage = /\.(jpg|jpeg|png|gif|bmp|svg)$/i.test(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
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>`;
} 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>`;
} else {
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${msg.timestamp}</div>`;
}
// 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 = `${profilePicture} <strong>${msg.sender}</strong><div class="message-sender-timestamp">${formatLocalTime(msg.timestamp)}</div>`;
messagesList.appendChild(usernameElement);
}
const messageElement = document.createElement('div');
messageElement.classList.add('message');
messageElement.dataset.messageId = msg.id;
messageElement.dataset.messageText = msg.content;
if (msg.content_type === 'text') {
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>`;
}
messagesList.appendChild(messageElement);
});
scrollToBottom();
})
.catch(error => console.error('Error fetching messages:', error));
} else if (msg.content_type === 'file') {
const isImage = isImageUrl(msg.content);
const isVideo = /\.(mp4|webm|ogg)$/i.test(msg.content);
if (isImage) {
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) {
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 {
messageElement.innerHTML = `<a href="/cdn/${msg.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(msg.timestamp)}</div>`;
}
}
messagesList.appendChild(messageElement);
});
scrollToBottom();
})
.catch(error => console.error('Error fetching messages:', error));
}
const sendMessageForm = document.querySelector('.send-message-form');
if (sendMessageForm) {
sendMessageForm.addEventListener('submit', function(event) {
@ -192,7 +249,7 @@ document.addEventListener('DOMContentLoaded', (event) => {
if (content || fileInput.files.length > 0) {
// Append the message locally
const newMessage = document.createElement('div');
newMessage.classList.add('message');
newMessage.classList.add('message');
newMessage.dataset.messageId = 'temp-id'; // Temporary ID for the new message
const currentTimestamp = new Date(timestamp).getTime();
const showUsername = previousSender !== username || shouldShowUsername(previousTimestamp, currentTimestamp);
@ -203,9 +260,12 @@ document.addEventListener('DOMContentLoaded', (event) => {
const usernameElement = document.createElement('div');
usernameElement.classList.add('message-sender');
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);
}
if (content) {
newMessage.dataset.messageText = content;

View file

@ -65,20 +65,21 @@ document.addEventListener('DOMContentLoaded', (event) => {
const showUsername = previousSender !== data.sender || shouldShowUsername(previousTimestamp, currentTimestamp);
previousSender = data.sender;
previousTimestamp = currentTimestamp;
const newMessage = document.createElement('div');
newMessage.classList.add('message');
newMessage.dataset.messageId = data.id;
newMessage.dataset.messageText = data.content;
if (showUsername) {
const usernameElement = document.createElement('div');
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);
}
if (data.content_type === 'text') {
if (isImageUrl(data.content)) {
newMessage.innerHTML = `<img src="${data.content}" alt="Image" class="enhanceable-image" style="max-width: 300px; max-height: 300px;" /><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
@ -96,10 +97,10 @@ document.addEventListener('DOMContentLoaded', (event) => {
newMessage.innerHTML = `<a href="/cdn/${data.content}" target="_blank">Download File</a><div class="timestamp">${formatLocalTime(data.timestamp)}</div>`;
}
}
messagesList.appendChild(newMessage);
scrollToBottom();
});
});
if (groupId) {
fetch(`/get_group_messages/${groupId}`)
@ -126,10 +127,12 @@ document.addEventListener('DOMContentLoaded', (event) => {
if (msg.sender === username) {
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);
}
const messageElement = document.createElement('div');
messageElement.classList.add('message');

View file

@ -624,6 +624,10 @@ video {
margin-top: 0.75rem;
}
.block {
display: block;
}
.flex {
display: flex;
}
@ -636,6 +640,10 @@ video {
height: 400px !important;
}
.\!h-\[465px\] {
height: 465px !important;
}
.\!h-fit {
height: -moz-fit-content !important;
height: fit-content !important;
@ -1178,6 +1186,23 @@ video {
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 {
--tw-text-opacity: 1 !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;
}
.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 *) {
--tw-bg-opacity: 1 !important;
background-color: rgb(38 38 38 / var(--tw-bg-opacity)) !important;

View file

@ -430,4 +430,20 @@ ul#friends-list {
.dropdown:hover .dropdown-content {display: block;}
/* 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>
<meta charset="UTF-8">
<title>Admin Panel</title>
{% include 'head.html' %}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
<script>
function changePassword(userId) {
@ -62,10 +63,10 @@
}
</script>
</head>
<body>
<body class="dark:!bg-black bg-white">
<div class="container">
<h1>Admin Panel</h1>
<table class="table">
<h1 class="dark:!text-white text-black">Admin Panel</h1>
<table class="table dark:!text-white text-black">
<thead>
<tr>
<th>Username</th>

View file

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

View file

@ -6,7 +6,7 @@
{% include 'head.html' %}
</head>
<body class="login-body">
<div class="login-div">
<div class="login-div !h-[465px]">
<form class="login-form" id="settings-form">
<img class="login-img" src="{{ url_for('static', filename='images/ms-style-logo.svg') }}" alt="Image">
<h2 class="login-h2">Settings</h2>
@ -18,13 +18,24 @@
<option value="dark" {% if current_theme == 'dark' %}selected{% endif %}>Dark</option>
</select>
</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">
<button class="ms-button-signin" type="submit" class="btn btn-primary">Save Settings</button>
</div>
</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>
<script>
@ -62,6 +73,12 @@
const messageDiv = document.getElementById('message');
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>
</body>
</html>

View file

@ -1,8 +1,5 @@
<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">
<h4 class="flex flex-row items-center">
<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>
@ -13,7 +10,7 @@
</h4>
<ul id="friends-list">
{% 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>
<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>
@ -30,15 +27,90 @@
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
<script>
// Function to refresh the page
function refreshPage() {
location.reload();
<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>
document.addEventListener('DOMContentLoaded', function() {
const contextMenu = document.getElementById('context-menu2');
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)
// setInterval(refreshPage, 2000);
</script>
document.addEventListener('click', function() {
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>