diff --git a/Transcendence/srcs/backend/routes/auth.js b/Transcendence/srcs/backend/routes/auth.js
index 8794ff2..833411f 100644
--- a/Transcendence/srcs/backend/routes/auth.js
+++ b/Transcendence/srcs/backend/routes/auth.js
@@ -26,6 +26,17 @@ router.post('/login', async(req, res) =>
res.status(result.status).json(result.data);
});
+router.post('/logout', async(req, res) =>
+{
+ const authHeader = req.headers['authorization'];
+ const token = authHeader && authHeader.split(' ')[1];
+ if (!token)
+ return (res.status(401).json({error: 'Missing token'}));
+
+ const result = await authService.logout(token);
+ res.status(result.status).json(result.data);
+});
+
router.get('/github', (req, res) => {
const githubAuthUrl = `https://github.com/login/oauth/authorize?` +
`client_id=${process.env.GITHUB_CLIENT_ID}&` +
diff --git a/Transcendence/srcs/backend/routes/avatar.js b/Transcendence/srcs/backend/routes/avatar.js
index 93844ff..ad9c923 100644
--- a/Transcendence/srcs/backend/routes/avatar.js
+++ b/Transcendence/srcs/backend/routes/avatar.js
@@ -25,7 +25,7 @@ router.post('/upload', authenticateToken, upload.single('avatar'), async(req, re
res.status(result.status).json(result.data);
});
-router.delete('/', authenticateToken, async(req, res) =>
+router.delete('/delete', authenticateToken, async(req, res) =>
{
const result = await avatarService.deleteAvatar(req.user.userId);
res.status(result.status).json(result.data);
diff --git a/Transcendence/srcs/backend/services/auth.js b/Transcendence/srcs/backend/services/auth.js
index 6e05d48..7ff7c6a 100644
--- a/Transcendence/srcs/backend/services/auth.js
+++ b/Transcendence/srcs/backend/services/auth.js
@@ -2,6 +2,30 @@ import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import {query} from '../db.js';
+async function logout(token)
+{
+ try
+ {
+ if (!token)
+ return ({status: 400, data: {error: 'Missing token'}});
+ try
+ {
+ jwt.verify(token, process.env.JWT_SECRET);
+ }
+ catch
+ {
+ return ({status: 401, data: {error: 'Invalid token'}});
+ }
+
+ return ({status: 200, data: {message: 'Logged out'}});
+ }
+ catch (err)
+ {
+ console.error(err);
+ return ({status: 500, data: {error: 'Server error'}});
+ }
+}
+
async function login(username, password)
{
try
@@ -60,4 +84,4 @@ async function register(username, password)
}
};
-export default {register, login};
+export default {register, login, logout};
diff --git a/Transcendence/srcs/backend/services/avatar.js b/Transcendence/srcs/backend/services/avatar.js
index ac59255..e933e8e 100644
--- a/Transcendence/srcs/backend/services/avatar.js
+++ b/Transcendence/srcs/backend/services/avatar.js
@@ -69,6 +69,9 @@ async function deleteAvatar(userId) {
if (currentAvatar === null)
return ({status: 404, data: {error: 'User not found'}});
+ if (currentAvatar === DEFAULT_AVATAR)
+ return ({status: 400, data: {error: 'Cannot delete default avatar'}});
+
// Reset the avatar to the default one
await setAvatar(DEFAULT_AVATAR, userId);
diff --git a/Transcendence/srcs/frontend/src/app.js b/Transcendence/srcs/frontend/src/app.js
index 0affa10..58adf1f 100644
--- a/Transcendence/srcs/frontend/src/app.js
+++ b/Transcendence/srcs/frontend/src/app.js
@@ -4,6 +4,7 @@
*/
import { windowRegistry } from './core/windows.js';
import { LoginWindow } from './windows/login.js';
+import { LogoutWindow } from './windows/logout.js';
import { GlobalChat } from './windows/global_chat.js';
import { AvatarWindow } from './windows/avatar.js';
import { FriendsWindow } from './windows/friends.js';
@@ -32,6 +33,7 @@ class App {
new FriendsWindow();
new GameRoomWindow();
new StatsWindow();
+ new LogoutWindow();
}
/**
@@ -49,7 +51,8 @@ class App {
'login': 'login',
'chat': 'chat',
'avatar': 'avatar',
- 'friends': 'friends'
+ 'friends': 'friends',
+ 'logout': 'logout'
};
// Event delegation on the menu
diff --git a/Transcendence/srcs/frontend/src/core/config.js b/Transcendence/srcs/frontend/src/core/config.js
index 22fff46..3b307f2 100644
--- a/Transcendence/srcs/frontend/src/core/config.js
+++ b/Transcendence/srcs/frontend/src/core/config.js
@@ -6,12 +6,14 @@
export const API = {
AUTH: {
LOGIN: '/api/auth/login',
+ LOGOUT: '/api/auth/logout',
REGISTER: '/api/auth/register',
GITHUB: '/api/auth/github'
},
AVATAR: {
GET: '/api/avatar/me',
- UPLOAD: '/api/avatar/upload'
+ UPLOAD: '/api/avatar/upload',
+ DELETE: '/api/avatar/delete'
},
FRIENDS: {
LIST: '/api/friends',
diff --git a/Transcendence/srcs/frontend/src/core/events.js b/Transcendence/srcs/frontend/src/core/events.js
index f323d30..7203296 100644
--- a/Transcendence/srcs/frontend/src/core/events.js
+++ b/Transcendence/srcs/frontend/src/core/events.js
@@ -82,6 +82,7 @@ export const Events = {
// Avatar
AVATAR_UPDATED: 'avatar:updated',
+ AVATAR_DELETED: 'avatar:deleted',
// Chat
CHAT_CONNECTED: 'chat:connected',
diff --git a/Transcendence/srcs/frontend/src/core/windows.js b/Transcendence/srcs/frontend/src/core/windows.js
index 994c524..ea9bdfa 100644
--- a/Transcendence/srcs/frontend/src/core/windows.js
+++ b/Transcendence/srcs/frontend/src/core/windows.js
@@ -228,6 +228,56 @@ export class Window {
return element;
}
+
+ NotficationContainer()
+ {
+ if (document.getElementById('notification-container')) return;
+
+ const container = this.createElement('div');
+ container.id = 'notification-container';
+ Object.assign(container.style, {
+ position: 'fixed',
+ top: '20px',
+ right: '20px',
+ zIndex: 1000,
+ display: 'flex',
+ flexDirection: 'column',
+ gap: '10px'
+ });
+ document.body.appendChild(container);
+ }
+
+ showNotification(message, color) {
+ this.NotficationContainer();
+ const container = document.getElementById('notification-container');
+ if (!container) return;
+
+ const notification = document.createElement('div');
+ notification.textContent = message;
+ Object.assign(notification.style, {
+ backgroundColor: color,
+ color: 'white',
+ padding: '10px 20px',
+ borderRadius: '5px',
+ boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
+ opacity: '0',
+ transform: 'translateY(-8px)',
+ transition: 'opacity 0.5s ease, transform 0.5s ease'
+ });
+
+ container.appendChild(notification);
+
+ requestAnimationFrame(() => {
+ notification.style.opacity = '1';
+ notification.style.transform = 'translateY(0)';
+ });
+
+ setTimeout(() => {
+ notification.style.opacity = '0';
+ notification.style.transform = 'translateY(-8px)';
+ setTimeout(() => notification.remove(), 500);
+ }, 2200);
+ }
}
// Export old class name for compatibility (alias)
diff --git a/Transcendence/srcs/frontend/src/index.html b/Transcendence/srcs/frontend/src/index.html
index 9a0e5c3..32f450c 100644
--- a/Transcendence/srcs/frontend/src/index.html
+++ b/Transcendence/srcs/frontend/src/index.html
@@ -17,6 +17,7 @@
+