Early Access: 1.0
This commit is contained in:
commit
761e32b95e
7 changed files with 636 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*.db
|
||||
secret.key
|
293
app.py
Normal file
293
app.py
Normal file
|
@ -0,0 +1,293 @@
|
|||
from flask import Flask, request, render_template, redirect, url_for, session, jsonify
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from cryptography.fernet import Fernet
|
||||
import os
|
||||
from datetime import datetime
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from flask_socketio import SocketIO, emit, join_room, leave_room
|
||||
|
||||
app = Flask(__name__)
|
||||
app.config['SECRET_KEY'] = os.urandom(24)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
|
||||
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
|
||||
db = SQLAlchemy(app)
|
||||
socketio = SocketIO(app)
|
||||
|
||||
# Function to generate a new encryption key
|
||||
def generate_key():
|
||||
key = Fernet.generate_key()
|
||||
with open("secret.key", "wb") as key_file:
|
||||
key_file.write(key)
|
||||
|
||||
# Function to load the encryption key
|
||||
def load_key():
|
||||
return open("secret.key", "rb").read()
|
||||
|
||||
# Ensure the key file exists
|
||||
if not os.path.exists("secret.key"):
|
||||
generate_key()
|
||||
|
||||
# Load the key
|
||||
key = load_key()
|
||||
cipher = Fernet(key)
|
||||
|
||||
class User(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
username = db.Column(db.String(150), unique=True, nullable=False)
|
||||
password = db.Column(db.String(150), nullable=False)
|
||||
online = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
class Message(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
sender = db.Column(db.String(150), nullable=False)
|
||||
receiver = db.Column(db.String(150), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
class PendingMessage(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
sender = db.Column(db.String(150), nullable=False)
|
||||
receiver = db.Column(db.String(150), nullable=False)
|
||||
content = db.Column(db.Text, nullable=False)
|
||||
timestamp = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
class Friend(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
friend_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
user = db.relationship('User', foreign_keys=[user_id])
|
||||
friend = db.relationship('User', foreign_keys=[friend_id])
|
||||
|
||||
class FriendRequest(db.Model):
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
sender_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
receiver_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
||||
status = db.Column(db.String(20), nullable=False, default='pending')
|
||||
sender = db.relationship('User', foreign_keys=[sender_id])
|
||||
receiver = db.relationship('User', foreign_keys=[receiver_id])
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
if 'username' in session:
|
||||
return redirect(url_for('dashboard'))
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/register', methods=['GET', 'POST'])
|
||||
def register():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
hashed_password = generate_password_hash(password) # Default method
|
||||
new_user = User(username=username, password=hashed_password)
|
||||
try:
|
||||
db.session.add(new_user)
|
||||
db.session.commit()
|
||||
return redirect(url_for('login'))
|
||||
except:
|
||||
return 'Username already exists!'
|
||||
return render_template('register.html')
|
||||
|
||||
@app.route('/login', methods=['GET', 'POST'])
|
||||
def login():
|
||||
if request.method == 'POST':
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user and check_password_hash(user.password, password):
|
||||
session['username'] = user.username
|
||||
return redirect(url_for('dashboard'))
|
||||
return 'Invalid credentials'
|
||||
return render_template('login.html')
|
||||
|
||||
@app.route('/dashboard')
|
||||
def dashboard():
|
||||
if 'username' in session:
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
friends = Friend.query.filter_by(user_id=user.id).all()
|
||||
friend_ids = [friend.friend_id for friend in friends]
|
||||
friend_users = User.query.filter(User.id.in_(friend_ids)).all()
|
||||
|
||||
friend_requests = FriendRequest.query.filter_by(receiver_id=user.id, status='pending').all()
|
||||
pending_messages = PendingMessage.query.filter_by(receiver=session['username']).all()
|
||||
messages = Message.query.filter_by(receiver=session['username']).all()
|
||||
|
||||
decrypted_pending_messages = [(msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp) for msg in pending_messages]
|
||||
decrypted_messages = [(msg.sender, cipher.decrypt(msg.content.encode()).decode(), msg.timestamp) for msg in messages]
|
||||
|
||||
return render_template('dashboard.html', username=session['username'], friends=friend_users, friend_requests=friend_requests, pending_messages=decrypted_pending_messages, messages=decrypted_messages)
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/send_message/<receiver>', methods=['POST'])
|
||||
def send_message(receiver):
|
||||
if 'username' in session:
|
||||
data = request.get_json()
|
||||
content = data['content']
|
||||
encrypted_content = cipher.encrypt(content.encode()).decode()
|
||||
|
||||
# Check if they are friends
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
receiver_user = User.query.filter_by(username=receiver).first()
|
||||
if not receiver_user:
|
||||
return jsonify({'error': 'User not found'}), 404
|
||||
friend = Friend.query.filter_by(user_id=user.id, friend_id=receiver_user.id).first()
|
||||
|
||||
if friend:
|
||||
new_message = Message(sender=session['username'], receiver=receiver, content=encrypted_content)
|
||||
db.session.add(new_message)
|
||||
db.session.commit()
|
||||
decrypted_content = cipher.decrypt(encrypted_content.encode()).decode()
|
||||
socketio.emit('new_message', {
|
||||
'sender': session['username'],
|
||||
'content': decrypted_content,
|
||||
'timestamp': new_message.timestamp.strftime("%Y-%m-%d %H:%M:%S")
|
||||
}, room=receiver)
|
||||
else:
|
||||
pending_message = PendingMessage(sender=session['username'], receiver=receiver, content=encrypted_content)
|
||||
db.session.add(pending_message)
|
||||
db.session.commit()
|
||||
|
||||
return jsonify({'status': 'Message sent'}), 200
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
@app.route('/get_messages/<friend_username>', methods=['GET'])
|
||||
def get_messages(friend_username):
|
||||
if 'username' in session:
|
||||
current_user = session['username']
|
||||
messages = Message.query.filter(
|
||||
((Message.sender == current_user) & (Message.receiver == friend_username)) |
|
||||
((Message.sender == friend_username) & (Message.receiver == current_user))
|
||||
).all()
|
||||
decrypted_messages = [
|
||||
{'sender': msg.sender, 'content': cipher.decrypt(msg.content.encode()).decode(), 'timestamp': msg.timestamp.strftime("%Y-%m-%d %H:%M:%S")}
|
||||
for msg in messages
|
||||
]
|
||||
return jsonify({'messages': decrypted_messages})
|
||||
return jsonify({'error': 'Unauthorized'}), 401
|
||||
|
||||
@app.route('/add_friend', methods=['POST'])
|
||||
def add_friend():
|
||||
if 'username' in session:
|
||||
friend_username = request.form['friend_username']
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
friend = User.query.filter_by(username=friend_username).first()
|
||||
if friend and user != friend:
|
||||
friend_request = FriendRequest(sender_id=user.id, receiver_id=friend.id)
|
||||
db.session.add(friend_request)
|
||||
db.session.commit()
|
||||
socketio.emit('friend_request', {
|
||||
'sender': session['username'],
|
||||
'receiver': friend_username
|
||||
}, room=friend_username)
|
||||
return redirect(url_for('dashboard'))
|
||||
return 'Friend not found or cannot add yourself as a friend'
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/accept_friend/<int:request_id>', methods=['POST'])
|
||||
def accept_friend(request_id):
|
||||
if 'username' in session:
|
||||
friend_request = FriendRequest.query.get(request_id)
|
||||
if friend_request and friend_request.receiver.username == session['username']:
|
||||
friend_request.status = 'accepted'
|
||||
db.session.commit()
|
||||
|
||||
# Create friendships both ways
|
||||
user_id = friend_request.receiver_id
|
||||
friend_id = friend_request.sender_id
|
||||
new_friend_1 = Friend(user_id=user_id, friend_id=friend_id)
|
||||
new_friend_2 = Friend(user_id=friend_id, friend_id=user_id)
|
||||
db.session.add(new_friend_1)
|
||||
db.session.add(new_friend_2)
|
||||
db.session.commit()
|
||||
|
||||
# Move pending messages to messages
|
||||
pending_messages = PendingMessage.query.filter_by(sender=friend_request.sender.username, receiver=friend_request.receiver.username).all()
|
||||
for pm in pending_messages:
|
||||
new_message = Message(sender=pm.sender, receiver=pm.receiver, content=pm.content, timestamp=pm.timestamp)
|
||||
db.session.add(new_message)
|
||||
db.session.delete(pm)
|
||||
db.session.commit()
|
||||
|
||||
socketio.emit('friend_request_accepted', {
|
||||
'sender': friend_request.sender.username,
|
||||
'receiver': friend_request.receiver.username
|
||||
}, room=friend_request.sender.username)
|
||||
|
||||
return redirect(url_for('dashboard'))
|
||||
return 'Friend request not found'
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/reject_friend/<int:request_id>', methods=['POST'])
|
||||
def reject_friend(request_id):
|
||||
if 'username' in session:
|
||||
friend_request = FriendRequest.query.get(request_id)
|
||||
if friend_request and friend_request.receiver.username == session['username']:
|
||||
db.session.delete(friend_request)
|
||||
db.session.commit()
|
||||
|
||||
socketio.emit('friend_request_rejected', {
|
||||
'sender': friend_request.sender.username,
|
||||
'receiver': friend_request.receiver.username
|
||||
}, room=friend_request.sender.username)
|
||||
|
||||
return redirect(url_for('dashboard'))
|
||||
return 'Friend request not found'
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/remove_friend/<int:friend_id>', methods=['POST'])
|
||||
def remove_friend(friend_id):
|
||||
if 'username' in session:
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
friend = Friend.query.filter_by(user_id=user.id, friend_id=friend_id).first()
|
||||
if friend:
|
||||
db.session.delete(friend)
|
||||
reciprocal_friend = Friend.query.filter_by(user_id=friend.friend_id, friend_id=user.id).first()
|
||||
if reciprocal_friend:
|
||||
db.session.delete(reciprocal_friend)
|
||||
db.session.commit()
|
||||
|
||||
socketio.emit('friend_removed', {
|
||||
'sender': session['username'],
|
||||
'receiver': friend.friend.username
|
||||
}, room=friend.friend.username)
|
||||
|
||||
return redirect(url_for('dashboard'))
|
||||
return 'Friend not found'
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
session.pop('username', None)
|
||||
return redirect(url_for('login'))
|
||||
|
||||
@socketio.on('join')
|
||||
def handle_join(data):
|
||||
username = data['username']
|
||||
join_room(username)
|
||||
|
||||
@socketio.on('leave')
|
||||
def handle_leave(data):
|
||||
username = data['username']
|
||||
leave_room(username)
|
||||
|
||||
@socketio.on('connect')
|
||||
def handle_connect():
|
||||
if 'username' in session:
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
if user:
|
||||
user.online = True
|
||||
db.session.commit()
|
||||
emit('user_online', {'username': user.username}, broadcast=True)
|
||||
|
||||
@socketio.on('disconnect')
|
||||
def handle_disconnect():
|
||||
if 'username' in session:
|
||||
user = User.query.filter_by(username=session['username']).first()
|
||||
if user:
|
||||
user.online = False
|
||||
db.session.commit()
|
||||
emit('user_offline', {'username': user.username}, broadcast=True)
|
||||
|
||||
if __name__ == '__main__':
|
||||
with app.app_context():
|
||||
db.create_all()
|
||||
socketio.run(app, debug=True)
|
101
static/styles.css
Normal file
101
static/styles.css
Normal file
|
@ -0,0 +1,101 @@
|
|||
body {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
}
|
||||
.sidebar {
|
||||
width: 250px;
|
||||
background-color: #2f3136;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
}
|
||||
.sidebar h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.sidebar ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
.sidebar ul li {
|
||||
margin: 10px 0;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.sidebar ul li .status-indicator {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 50%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.online{
|
||||
margin-right: 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background-color: #23a55a;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.offline{
|
||||
margin-right: 10px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background-color: #80848e;
|
||||
border-radius: 50%;
|
||||
}
|
||||
.chat-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.chat-header {
|
||||
background-color: #36393f;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.messages {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.message {
|
||||
margin-bottom: 10px;
|
||||
position: relative;
|
||||
}
|
||||
.message strong {
|
||||
color: #7289da;
|
||||
}
|
||||
.message .timestamp {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -20px;
|
||||
right: 0;
|
||||
background-color: rgba(0,0,0,0.7);
|
||||
color: white;
|
||||
padding: 5px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
.message:hover .timestamp {
|
||||
display: block;
|
||||
}
|
||||
.send-message-form {
|
||||
display: flex;
|
||||
}
|
||||
.send-message-form input {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
margin-right: 10px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.send-message-form button {
|
||||
padding: 10px;
|
||||
border: none;
|
||||
background-color: #7289da;
|
||||
color: white;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
}
|
186
templates/dashboard.html
Normal file
186
templates/dashboard.html
Normal file
|
@ -0,0 +1,186 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Dashboard</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
<script src="https://cdn.socket.io/4.0.1/socket.io.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
document.addEventListener('DOMContentLoaded', (event) => {
|
||||
const socket = io();
|
||||
socket.emit('join', { username: "{{ username }}" });
|
||||
|
||||
socket.on('new_message', function(data) {
|
||||
const messagesList = document.getElementById('messages');
|
||||
const newMessage = document.createElement('div');
|
||||
newMessage.classList.add('message');
|
||||
newMessage.innerHTML = `<strong>${data.sender}</strong>: ${data.content}<div class="timestamp">${data.timestamp}</div>`;
|
||||
messagesList.appendChild(newMessage);
|
||||
});
|
||||
|
||||
socket.on('friend_request', function(data) {
|
||||
const friendRequestsList = document.getElementById('friend-requests');
|
||||
const newRequest = document.createElement('li');
|
||||
newRequest.innerHTML = `
|
||||
${data.sender}
|
||||
<form method="POST" action="/accept_friend/${data.request_id}">
|
||||
<button type="submit">Accept</button>
|
||||
</form>
|
||||
<form method="POST" action="/reject_friend/${data.request_id}">
|
||||
<button type="submit">Reject</button>
|
||||
</form>
|
||||
`;
|
||||
friendRequestsList.appendChild(newRequest);
|
||||
checkFriendRequests();
|
||||
});
|
||||
|
||||
socket.on('friend_request_accepted', function(data) {
|
||||
const friendList = document.getElementById('friends');
|
||||
const newFriend = document.createElement('li');
|
||||
newFriend.innerHTML = `
|
||||
<div class="status-indicator ${data.online ? 'online' : 'offline'}"></div>
|
||||
${data.sender}
|
||||
<form class="send-message-form" data-receiver="${data.sender}" method="POST">
|
||||
<input type="text" name="content" placeholder="Type a message" required>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
`;
|
||||
friendList.appendChild(newFriend);
|
||||
checkFriendRequests();
|
||||
});
|
||||
|
||||
socket.on('user_online', function(data) {
|
||||
const friendElements = document.querySelectorAll(`.friend[data-username="${data.username}"] .status-indicator`);
|
||||
friendElements.forEach(el => el.classList.remove('offline'));
|
||||
friendElements.forEach(el => el.classList.add('online'));
|
||||
});
|
||||
|
||||
socket.on('user_offline', function(data) {
|
||||
const friendElements = document.querySelectorAll(`.friend[data-username="${data.username}"] .status-indicator`);
|
||||
friendElements.forEach(el => el.classList.remove('online'));
|
||||
friendElements.forEach(el => el.classList.add('offline'));
|
||||
});
|
||||
|
||||
const friends = document.querySelectorAll('.friend');
|
||||
friends.forEach(friend => {
|
||||
friend.addEventListener('click', function() {
|
||||
const friendName = this.dataset.username;
|
||||
loadMessages(friendName);
|
||||
});
|
||||
});
|
||||
|
||||
function loadMessages(friendName) {
|
||||
fetch(`/get_messages/${friendName}`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const messagesList = document.getElementById('messages');
|
||||
messagesList.innerHTML = '';
|
||||
document.getElementById('chat-with').textContent = `Chatting with ${friendName}`;
|
||||
document.getElementById('send-message-form').dataset.receiver = friendName;
|
||||
data.messages.forEach(msg => {
|
||||
const messageElement = document.createElement('div');
|
||||
messageElement.classList.add('message');
|
||||
messageElement.innerHTML = `<strong>${msg.sender}</strong>: ${msg.content}<div class="timestamp">${msg.timestamp}</div>`;
|
||||
messagesList.appendChild(messageElement);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function checkFriendRequests() {
|
||||
const friendRequestsList = document.getElementById('friend-requests');
|
||||
const friendRequestsSection = document.getElementById('friend-requests-section');
|
||||
if (friendRequestsList.children.length === 0) {
|
||||
friendRequestsSection.style.display = 'none';
|
||||
} else {
|
||||
friendRequestsSection.style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
checkFriendRequests();
|
||||
|
||||
document.querySelectorAll('.send-message-form').forEach(form => {
|
||||
form.addEventListener('submit', function(event) {
|
||||
event.preventDefault();
|
||||
const receiver = form.dataset.receiver;
|
||||
const content = form.querySelector('input[name="content"]').value;
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
// Append the message locally
|
||||
const messagesList = document.getElementById('messages');
|
||||
const newMessage = document.createElement('div');
|
||||
newMessage.classList.add('message');
|
||||
newMessage.innerHTML = `<strong>{{ username }}</strong>: ${content}<div class="timestamp">${timestamp}</div>`;
|
||||
messagesList.appendChild(newMessage);
|
||||
|
||||
fetch(`/send_message/${receiver}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ content: content })
|
||||
}).then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'Message sent') {
|
||||
form.querySelector('input[name="content"]').value = '';
|
||||
} else {
|
||||
alert('Message sending failed');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="sidebar">
|
||||
<h3>Welcome, {{ username }}</h3>
|
||||
<form method="POST" action="{{ url_for('add_friend') }}">
|
||||
<label for="friend_username">Add Friend:</label>
|
||||
<input type="text" id="friend_username" name="friend_username" required>
|
||||
<button type="submit">Add Friend</button>
|
||||
</form>
|
||||
<a href="{{ url_for('logout') }}" class="btn btn-danger mt-3">Logout</a>
|
||||
|
||||
<div id="friend-requests-section">
|
||||
<h4>Friend Requests</h4>
|
||||
<ul id="friend-requests">
|
||||
{% for request in friend_requests %}
|
||||
<li>
|
||||
{{ request.sender.username }}
|
||||
<form method="POST" action="{{ url_for('accept_friend', request_id=request.id) }}">
|
||||
<button type="submit">Accept</button>
|
||||
</form>
|
||||
<form method="POST" action="{{ url_for('reject_friend', request_id=request.id) }}">
|
||||
<button type="submit">Reject</button>
|
||||
</form>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>Friends</h4>
|
||||
<ul id="friends">
|
||||
{% for friend in friends %}
|
||||
<li class="friend" data-username="{{ friend.username }}">
|
||||
<div class="status-indicator {{ 'online' if friend.online else 'offline' }}"></div>
|
||||
{{ friend.username }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="chat-container">
|
||||
<div class="chat-header">
|
||||
<h3 id="chat-with">Select a friend to start chatting</h3>
|
||||
</div>
|
||||
<div class="messages" id="messages">
|
||||
<!-- Messages will be displayed here -->
|
||||
</div>
|
||||
<form class="send-message-form" id="send-message-form" data-receiver="" method="POST">
|
||||
<input type="text" name="content" placeholder="Type a message" required>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
18
templates/login.html
Normal file
18
templates/login.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Login</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Login</h2>
|
||||
<form method="POST">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required><br>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required><br>
|
||||
<button type="submit">Login</button>
|
||||
</form>
|
||||
<a href="{{ url_for('register') }}">Register</a>
|
||||
</body>
|
||||
</html>
|
18
templates/register.html
Normal file
18
templates/register.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Register</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Register</h2>
|
||||
<form method="POST">
|
||||
<label for="username">Username:</label>
|
||||
<input type="text" id="username" name="username" required><br>
|
||||
<label for="password">Password:</label>
|
||||
<input type="password" id="password" name="password" required><br>
|
||||
<button type="submit">Register</button>
|
||||
</form>
|
||||
<a href="{{ url_for('login') }}">Login</a>
|
||||
</body>
|
||||
</html>
|
18
templates/send_message.html
Normal file
18
templates/send_message.html
Normal file
|
@ -0,0 +1,18 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Send Message</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Send Message</h2>
|
||||
<form method="POST">
|
||||
<label for="receiver">To:</label>
|
||||
<input type="text" id="receiver" name="receiver" required><br>
|
||||
<label for="content">Message:</label>
|
||||
<textarea id="content" name="content" required></textarea><br>
|
||||
<button type="submit">Send</button>
|
||||
</form>
|
||||
<a href="{{ url_for('dashboard') }}">Back to Dashboard</a>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue