diff --git a/Transcendence/srcs/backend/index.js b/Transcendence/srcs/backend/index.js index 1a80d5a..28b9dd5 100644 --- a/Transcendence/srcs/backend/index.js +++ b/Transcendence/srcs/backend/index.js @@ -12,6 +12,7 @@ import playerStatsRouter from './routes/player_stats.js'; import {waitForDb, createTables, runMigrations, ensureOauthClient} from './db.js'; import setupSocketIO from './services/socket.js'; import avatarService from './services/avatar.js'; +import intraRouter from './routes/intra.js'; const app = express(); const httpsOptions = { @@ -53,6 +54,7 @@ async function startServer() app.use('/api/avatar', avatarRouter); app.use('/api/friends', friendsRouter); app.use('/api/stats', playerStatsRouter); + app.use('/api/intra', intraRouter); app.get('/api', (req, res) => res.send('Backend running')); server.listen(3001, () => diff --git a/Transcendence/srcs/backend/routes/intra.js b/Transcendence/srcs/backend/routes/intra.js new file mode 100644 index 0000000..51aacda --- /dev/null +++ b/Transcendence/srcs/backend/routes/intra.js @@ -0,0 +1,53 @@ +// routes/intra.js +import express from 'express'; + +const router = express.Router(); + +let token; + +async function set_token() { + try { + const response = await fetch("https://api.intra.42.fr/oauth/token", { + method: "POST", + body: new URLSearchParams({ + grant_type: "client_credentials", + client_id: process.env.INTRA_CLIENT_ID, + client_secret: process.env.INTRA_CLIENT_SECRET + }), + headers: { + "Content-Type": "application/x-www-form-urlencoded" + } + }); + + token = await response.json(); + + setTimeout(set_token, (token.expires_in - 60) * 1000); + } catch (e) { + console.error("Token error:", e); + } +} +set_token(); + + +router.get('/profile/:login', async (req, res) => { + try { + const response = await fetch( + `https://api.intra.42.fr/v2/users/${req.params.login}`, + { + headers: { + Authorization: `Bearer ${token.access_token}` + } + } + ); + + if (!response.ok) { + return res.status(response.status).json({ error: 'User not found' }); + } + + res.json(await response.json()); + } catch (e) { + res.status(500).json({ error: 'Failed to fetch profile' }); + } +}); + +export default router; \ No newline at end of file diff --git a/Transcendence/srcs/frontend/src/wiskas/popup.js b/Transcendence/srcs/frontend/src/wiskas/popup.js new file mode 100644 index 0000000..af0ed07 --- /dev/null +++ b/Transcendence/srcs/frontend/src/wiskas/popup.js @@ -0,0 +1,53 @@ +import { colorizeText, updateElement } from "./tools.js"; + +/* /////////////////////////////////////////// */ +export class Popup { + + constructor(msg, parent = document.body) { + this.msg = msg; + this.parent = parent; + this.obj = updateElement({ + parent: parent, + classList: ['popup'], + additionalStyles: { + opacity: '0' + } + }); + + this.run(); + } + + async create() { + this.parent.appendChild(this.obj); + + this.obj.style.transition = "opacity 0.5s ease"; + requestAnimationFrame(() => { + this.obj.style.opacity = "1"; + }); + await new Promise(r => setTimeout(r, 500)); + } + async write(speed = 30) { + for (let i = 0; i < this.msg.length; i++) { + this.obj.textContent += this.msg[i]; + // colorizeText(); + await new Promise(r => setTimeout(r, speed)); + } + } + async remove() { + + await new Promise(r => setTimeout(r, 2000)); + this.obj.style.transition = "opacity 0.3s ease"; + this.obj.style.opacity = "0"; + await new Promise(r => setTimeout(r, 300)); + if (this.obj.parentNode) { + this.obj.parentNode.removeChild(this.obj); + } + } + async run() { + await this.create(); + await this.write(); + await this.remove(); + } +} + + diff --git a/Transcendence/srcs/frontend/src/wiskas/tools.js b/Transcendence/srcs/frontend/src/wiskas/tools.js new file mode 100644 index 0000000..18688f9 --- /dev/null +++ b/Transcendence/srcs/frontend/src/wiskas/tools.js @@ -0,0 +1,61 @@ +/* /////////////////////////////////////////// */ +// render in color the text of all .multicolor +export function colorizeText() { + + const elements = document.querySelectorAll(".multicolor"); + + const colorizeText = (el) => { + const text = el.textContent; + el.innerHTML = ""; + + const baseHue = Math.random() * 360; + + // 🎲 random step = makes rainbow "scrambled" + const step = (Math.random() * 60) + 10; // 10 → 70 + + // 🎲 random direction (left or right rainbow) + const direction = Math.random() < 0.5 ? 1 : -1; + + [...text].forEach((char, i) => { + const span = document.createElement("span"); + span.textContent = char; + + const hue = baseHue + (i * step * direction); + + span.style.color = `hsl(${hue}, 90%, 60%)`; + + span.style.textShadow = `1px 1px 0 rgba(0,0,0,0.3)`; + + el.appendChild(span); + }); + }; + elements.forEach(colorizeText); +} + +export function updateElement({ + el, // existing element or null to create new + parent = document.body, + id = null, + classList = [], // object like { css - classes to add } + textContent = "", + additionalStyles = {} // object like { color: 'red', display: 'flex' } +} = {}) { + // If no element passed, create a div by default + if (!el) { + el = document.createElement('div'); + parent.appendChild(el); + } + + // Set ID if provided + if (id) el.id = id; + + // Manage classes + classList.forEach(cls => el.classList.add(cls)); + + // Set text content + if (textContent !== undefined) el.textContent = textContent; + + Object.assign(el.style, additionalStyles); + + return el; +} \ No newline at end of file diff --git a/Transcendence/srcs/frontend/src/wiskas/wiskas.css b/Transcendence/srcs/frontend/src/wiskas/wiskas.css index c27827d..ca94f5a 100644 --- a/Transcendence/srcs/frontend/src/wiskas/wiskas.css +++ b/Transcendence/srcs/frontend/src/wiskas/wiskas.css @@ -1,79 +1,106 @@ -:root { - --color-primary: #ffc75e; - --color-primary-hover: #ffc75e; - --color-success: #00c71b; - --color-success-dark: #ffc75e; - --color-error: #ff4d4d; - --color-warning: #ffc75e; - --color-github: #ffc75e; - - --color-bg: #ffe5b5; - - --color-surface: #ffcc00; - --color-surface-light: #feffa6; - --color-text: #000000; - --color-text-muted: #353535; - - --font-size-base: 10px; - --font-size-sm: 1.2rem; - --font-size-md: 1.4rem; - --font-size-lg: 1.6rem; - --font-size-xl: 3rem; - - --spacing-xs: 4px; - --spacing-sm: 8px; - --spacing-md: 12px; - --spacing-lg: 16px; - --spacing-xl: 24px; - - --radius-sm: 4px; - --radius-md: 6px; - --radius-lg: 12px; - --radius-full: 50%; - - --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3); - --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5); - --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5); - - --transition-fast: 150ms ease; - --transition-normal: 250ms ease; - - --z-menu: 2; - --z-window: 100; - --z-modal: 200; -} - -.game { - position: fixed; - top: var(--spacing-lg); - right: 50px; - +body { + margin: 0; + height: 100vh; display: flex; - flex-direction: column; - gap: var(--spacing-lg); - - z-index: var(--z-menu); + justify-content: center; + align-items: center; + background: linear-gradient(135deg, #1f001f, #1f1f00); + overflow: hidden; } -.game__item { - background: var(--color-surface); - color: var(--color-text); - border: 1px solid var(--color-surface-light); - border-radius: var(--radius-lg); - border-color: #fda725; - padding: var(--spacing-sm) var(--spacing-md); - font-size: var(--font-size-md); - cursor: pointer; - transition: all var(--transition-fast); - text-align: center; +h1 { + font-size: 4rem; + color: white; + animation: float 2s ease-in-out infinite alternate; + border: 5px double #654050; } -.game__item:hover { +@keyframes float { + from { transform: translateY(0); } + to { transform: translateY(-20px); } +} + +/* /////////////////////////////////////////// */ +.box:hover { background: var(--color-surface-light); font-size: var(--font-size-lg); + + animation: bobble 0.4s ease-in-out infinite alternate; } -.game__item--active { - background: var(--color-primary); - border-color: var(--color-primary); +@keyframes bobble { + from { + transform: translateX(-5px); + } + to { + transform: translateX(5px); + } } + +.box { + background: #142d4a; + height: 100px; + + margin: 15px; + aspect-ratio: 1/1; + border-radius: 25px; + position: fixed; + top: 0; + right: 0; + display: flex; + justify-content: center; + align-items: center; + font-family: "Roboto"; + font-size: 15px; + z-index: 10; +} + +@property --deg { + syntax: ''; + inherits: true; + initial-value: 0deg; +} + +.box::before, +.box::after { + content: ""; + position: absolute; + height: 100%; + width: 100%; + background: conic-gradient( + from var(--deg) at center, + #00c3ff, + #4d0199, + #6300c6, + #009dcd + ); + border-radius: inherit; + z-index: -2; + padding: 2px; + animation: autoRotate 2s linear infinite; +} +.box::after { + filter: blur(10px); +} +@keyframes autoRotate { + to{ --deg: 360deg; } +} + +/* /////////////////////////////////////////// */ +.popup { + max-width: 300px; + background-color: #41030c; + margin: 30px; + margin-right: -50px; + padding: 1em; + font-weight: 700; + color: #ffffff; + font-size: 20px; + text-align: center; + border: 10px solid var(--clr-accent); + border-radius: 50px; + position: fixed; + right: 50%; + top: 50%; + z-index: 999; +} \ No newline at end of file diff --git a/Transcendence/srcs/frontend/src/wiskas/wiskas.html b/Transcendence/srcs/frontend/src/wiskas/wiskas.html index 0a7424f..933a5b4 100644 --- a/Transcendence/srcs/frontend/src/wiskas/wiskas.html +++ b/Transcendence/srcs/frontend/src/wiskas/wiskas.html @@ -5,45 +5,24 @@ Wiskas -

METS TON CHAT ICI

+

+ - +
+ + \ No newline at end of file diff --git a/Transcendence/srcs/frontend/src/wiskas/wiskas.js b/Transcendence/srcs/frontend/src/wiskas/wiskas.js new file mode 100644 index 0000000..c45872a --- /dev/null +++ b/Transcendence/srcs/frontend/src/wiskas/wiskas.js @@ -0,0 +1,113 @@ +import { Popup } from "./popup.js"; +import { colorizeText, updateElement } from "./tools.js"; + + +/* /////////////////////////////////////////// */ + + + +/* /////////////////////////////////////////// */ +// 2️⃣ Add click handler +async function tryLogin() { + const login = prompt("Enter your 42 login:"); // Ask for a login + + if (!login) return; + + try { + // Call your backend route + const res = await fetch(`/api/intra/profile/${login}`); + + if (!res.ok) { + new Popup('Please, who do you think we are?\nWe already know all about you.\nNow enter your correct login and nobody gets hurt'); + const errorData = await res.json(); + return null; + } + + const data = await res.json(); + console.log("Profile data:", data); + return data; + } catch (err) { + console.error("Fetch failed:", err); + return null; + } +} + +/* /////////////////////////////////////////// */ +export class Wiskas { + constructor(parent = document.body) { + this.parent = parent; + this.obj = updateElement({ + parent: parent, + classList: ['wiskas'] + }) + + this.json_login = ''; + this.index_chaberu = 0; + this.iniChat(); + } + + chaberu() { + let num = Math.min(this.index_chaberu, this.discussions.length - 1); + let text = this.discussions[num]; + new Popup(text, this.obj); + this.index_chaberu++; + } + + iniChat() { + this.discussions = ['Well hi there...', + 'Please refrain from touching\n the yellow button without\n beeing logged in', + 'We are going to take actions\n if you continue..', + 'Actions already taken\n you are only making it worse']; + } + + async login() { + let answer = await tryLogin(); + if (!answer) return; + this.json_login = answer; + + let dataUser = { + firstName: this.json_login.usual_first_name ?? this.json_login.first_name, + lastName: this.json_login.last_name, + photo: this.json_login.image.link, + month: this.json_login.pool_month, + year: this.json_login.pool_year, + projects: this.json_login.projects_users.filter(project => project.status === "in_progress").map(project => project.project.name), + perfect: this.json_login.projects_users.filter(project => project.final_mark === 125).map(project => project.project.name), + + }; + this.discussions = [ + `Welcome ${dataUser.firstName} ${dataUser.lastName}.`, + `We heard quite a lot about the piscine of ${dataUser.month} ${dataUser.year}...\nIt's suprising to see you here`, + `How is your ${dataUser.projects[Math.floor(Math.random() * dataUser.projects.length)]} coming along?`, + `Perfect score for ${dataUser.perfect[Math.floor(Math.random() * dataUser.perfect.length)]}, impressive.. Should you really spend so much time in front of a screen?`, + `Shouldn't you be working on your ${dataUser.projects[Math.floor(Math.random() * dataUser.projects.length)]}?`, + `Quite an ugly human...\n but then again, you arent a cat` + ]; + } +} + + +/* /////////////////////////////////////////// */ +let el = document.getElementById('helloText'); +let img = document.createElement('img'); +img.src = '../assets/wiskas-the-third.jpg'; +el.append(img); +/* /////////////////////////////////////////// */ +colorizeText(); +/* /////////////////////////////////////////// */ +// 1️⃣ Create a button dynamically +const app = document.getElementById('login'); + + +let cat = new Wiskas; + +let buttonLogin = document.getElementById('login-button'); +Object.assign(buttonLogin.style, { + position: 'fixed', // make sure it's fixed + top: '0', + left: '0', + right: 'auto' // remove the right: 0 if it comes from CSS +}); +buttonLogin.addEventListener('click', () => cat.login()); +img.addEventListener('click', () => { cat.chaberu() }) +