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 @@ +