Compare commits

..

26 Commits

Author SHA1 Message Date
Lara REALI
cf0edf5bc0 Merge remote-tracking branch 'origin/apommier' into ereali 2023-06-24 20:18:46 +02:00
Lara REALI
92f3f496de Friend request css + change navbar Social+ button border qr butonn desactivate qr 2023-06-24 20:02:14 +02:00
Alexandre POMMIER
c6fe3a5a99 fix invite and match history not done 2023-06-24 19:39:16 +02:00
Alexandre POMMIER
38052b5034 fix invit 2023-06-24 18:19:41 +02:00
Alexandre POMMIER
fc280662b9 merge 2023-06-24 16:17:16 +02:00
Lara REALI
b04683576d merge apommier 2023-06-24 15:52:24 +02:00
Lara REALI
c011ea2b04 merge 2023-06-24 15:50:10 +02:00
Alexandre POMMIER
e44320a88f Merge remote-tracking branch 'origin/ereali' into apommier 2023-06-24 15:39:27 +02:00
Alexandre POMMIER
cf7c648813 before merge 2023-06-24 15:38:16 +02:00
Elisee ADJIGUIDI
a84b932355 merge 2023-06-24 15:14:08 +02:00
Elisee ADJIGUIDI
44fb3cdecd responsive pages/game nickname already Taken/Nickname too short 2023-06-24 14:35:50 +02:00
Lara REALI
6fd9660bff message sans decalage 2023-06-24 01:04:47 +02:00
Lara REALI
bdeb414c09 border message + responsive on profile 2023-06-24 00:51:36 +02:00
Alexandre POMMIER
071f30764a canvas background page, fix message mute, win loss in profile 2023-06-24 00:51:32 +02:00
Elisee ADJIGUIDI
de59d21671 change 2023-06-24 00:37:20 +02:00
Elisee ADJIGUIDI
6ad04baf06 merge 2023-06-23 20:32:35 +02:00
Alexandre POMMIER
d5e3532bd0 merge all all 2023-06-23 20:30:14 +02:00
Alexandre POMMIER
6d1cd933e2 Merge remote-tracking branch 'origin/ereali' into apommier 2023-06-23 20:21:36 +02:00
Alexandre POMMIER
b50d789d1d merge all syd 2023-06-23 20:20:10 +02:00
Alexandre POMMIER
c07a169794 fix mute ? fix socket fix infinite request profile, modify port 2023-06-23 19:59:23 +02:00
Elisee ADJIGUIDI
72a4f42cdb change design 2023-06-23 19:59:07 +02:00
Lara REALI
4d98765009 margin on leaderboard pic + auto input qr 2023-06-23 19:58:45 +02:00
Lara REALI
b27cb189b3 Qrcode number without arrows 2023-06-23 18:45:59 +02:00
Elisee ADJIGUIDI
0d42eda749 change 2023-06-23 15:38:58 +02:00
Little Dipper
f9a26e8e0f longeur messages et logueur modal nouveau message 2023-06-18 23:04:49 +02:00
Little Dipper
263e3332a1 js to ts 2023-06-18 21:22:25 +02:00
36 changed files with 599 additions and 498 deletions

11
.env
View File

@ -12,14 +12,9 @@
#URL
NGINX_ENVSUBST_TEMPLATE_SUFFIX=".conf"
# BASE_URL=http://localhost
BASE_URL=localhost:8080
REACT_APP_BASE_URL=localhost:8080
REDIRECT_URI=http://localhost:8080/api/auth/login
#postgres var
# POSTGRES_HOST=127.0.0.1
# DB_TYPE=postgres
BASE_URL=bess-f2r2s16:8080
REACT_APP_BASE_URL=bess-f2r2s16:8080
REDIRECT_URI=http://bess-f2r2s16:8080/api/auth/login
POSTGRES_HOST=postgresql
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres

3
.gitignore vendored
View File

@ -1,4 +1,5 @@
#.env
.env
containers/react/.env
backend/node_modules/
containers/backend/dist/

153
README.md
View File

@ -1,153 +0,0 @@
# ft_transcendence
## Description
ft_transcendence est le projet final du tronc commun de l'École 42. Il s'agit de créer une application web complète permettant de jouer au Pong en ligne avec des fonctionnalités modernes comme les tournois, le chat en temps réel, et l'authentification.
## Fonctionnalités principales
- 🎮 **Jeu Pong** en temps réel multijoueur
- 🏆 **Système de tournois** avec brackets
- 💬 **Chat en temps réel** avec channels
- 👤 **Profils utilisateurs** et amis
- 🔐 **Authentification 2FA** (Google, 42)
- 📊 **Statistiques** et historique des parties
- 🎨 **Interface moderne** et responsive
- 🔒 **Sécurité** web avancée
## Technologies utilisées
### Frontend
- **Framework** : React/Vue.js/Angular ou Vanilla JS
- **Styling** : CSS3, Bootstrap/Tailwind
- **WebSockets** : Pour le temps réel
### Backend
- **Framework** : Django/Flask/Node.js
- **Base de données** : PostgreSQL
- **API** : REST ou GraphQL
- **Authentication** : OAuth2, JWT
### DevOps
- **Containerisation** : Docker & Docker Compose
- **Reverse Proxy** : Nginx
- **SSL/TLS** : Certificats HTTPS
- **Base de données** : PostgreSQL en conteneur
## Architecture
```
ft_transcendence/
├── docker-compose.yml # Orchestration des services
├── frontend/ # Application client
│ ├── src/
│ ├── public/
│ └── Dockerfile
├── backend/ # API serveur
│ ├── api/
│ ├── models/
│ ├── services/
│ └── Dockerfile
├── database/ # Configuration PostgreSQL
├── nginx/ # Configuration reverse proxy
└── ssl/ # Certificats SSL
```
## Modules bonus
- **Module Web** : Framework moderne (React/Vue/Angular)
- **Module User Management** : Authentification avancée
- **Module Gaming** : Variantes de Pong ou autres jeux
- **Module AI-Algo** : IA pour jouer contre
- **Module Cybersecurity** : Sécurité renforcée
- **Module DevOps** : Infrastructure monitoring
- **Module Graphics** : Interface 3D avancée
## Installation et déploiement
### Prérequis
- Docker et Docker Compose
- Domaine avec certificat SSL
- Clés API (42, Google) pour OAuth
### Lancement
```bash
git clone <repository-url>
cd ft_transcendence
docker-compose up --build
```
### Configuration
1. Configurer les variables d'environnement
2. Générer les certificats SSL
3. Configurer OAuth applications
4. Initialiser la base de données
## Gameplay
### Pong multijoueur
- Contrôles clavier fluides
- Synchronisation en temps réel
- Système de score et timer
- Reconnexion automatique
### Tournois
- Création de tournois public/privé
- Système d'élimination
- Classements et récompenses
- Notifications en temps réel
## Sécurité implémentée
- 🔒 **HTTPS** obligatoire
- 🛡️ **Protection CSRF/XSS**
- 🔑 **Authentification 2FA**
- 💾 **Hashage sécurisé** des mots de passe
- 🚫 **Protection injection SQL**
- 🔐 **Validation** côté serveur
- 🍪 **Gestion sécurisée** des sessions
## Fonctionnalités sociales
- **Profils utilisateurs** personnalisables
- **Système d'amis** avec invitations
- **Chat global** et channels privés
- **Statut en ligne** des utilisateurs
- **Historique des parties**
- **Achievements** et badges
## Performance et scalabilité
- **WebSockets** pour le temps réel
- **Cache Redis** pour les sessions
- **Optimisation** des requêtes DB
- **Load balancing** pour la montée en charge
- **Monitoring** des performances
## Tests et qualité
- **Tests unitaires** backend
- **Tests d'intégration** API
- **Tests end-to-end** Selenium
- **Sécurité** avec OWASP ZAP
- **Performance** avec JMeter
## Compétences développées
- Architecture d'applications web modernes
- Développement full-stack
- Sécurité web avancée
- DevOps et containerisation
- Gestion de projet complexe
- Programmation temps réel
- UI/UX design
## Contraintes 42
- **Single Page Application** obligatoire
- **3 frameworks/langages** différents minimum
- **Docker** pour tous les services
- **Sécurité** de niveau production
- **Code maintenable** et documenté
## Auteur
Alexandre Pommier (apommier) - École 42
## Équipe (si applicable)
- Développeur Frontend
- Développeur Backend
- DevOps Engineer
- Security Specialist
## Licence
Projet académique - École 42

View File

@ -1 +1 @@
ALTER USER postgres WITH PASSWORD 'pass';
ALTER USER postgres WITH PASSWORD 'postgres';

View File

@ -10,7 +10,7 @@ server {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://react_app:8001;
proxy_pass http://react_app:8081;
}
location /api {
@ -21,7 +21,7 @@ server {
proxy_pass http://api:3000/api;
}
location /socket {
location /socket.io {
# Forward requests to socket server running on port 4001
if ($request_uri ~ ^/socket/4001) {
proxy_pass http://chat:4001;

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/17 01:00:00 by apommier #+# #+# */
/* Updated: 2023/06/21 01:19:01 by apommier ### ########.fr */
/* Updated: 2023/06/23 23:24:16 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -180,6 +180,9 @@ export class AppController {
// let user = req.user
// user.nickname = data.nickname
console.log(`user= ${req.user.username}`)
const taken = await this.userService.findNickname(data.nickname)
if (taken)
return (0);
let user = await this.userService.findOne(req.user.username)
user.nickname = data.nickname;
// return await this.userService.getFriends(req.user.username);
@ -480,8 +483,6 @@ export class AppController {
// res.json(messages);
}
@UseGuards(JwtAuthGuard)
@Get('/conv')
async getConv(@Request() req) {
@ -588,7 +589,7 @@ export class AppController {
async muteUser(@Body() data: any) {
if (!data.username)
return ;
return await this.chatService.muteUser(data.convId, data.username)
return await this.chatService.muteUser(data.convId, data.username, data.time)
}
@UseGuards(JwtAuthGuard)
@ -598,11 +599,16 @@ export class AppController {
return await this.chatService.isAdmin(data.convId, req.user.username)
}
@UseGuards(JwtAuthGuard)
@Post('/private')
async setPrivate(@Body() data: any) {
return await this.chatService.setPrivate(data.convId)
return await this.chatService.setPrivate(data.convId, true)
}
@UseGuards(JwtAuthGuard)
@Post('/public')
async setPublic(@Body() data: any) {
return await this.chatService.setPrivate(data.convId, false)
}
@UseGuards(JwtAuthGuard)

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/17 01:00:25 by apommier #+# #+# */
/* Updated: 2023/06/20 16:47:02 by apommier ### ########.fr */
/* Updated: 2023/06/24 18:47:59 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -119,14 +119,22 @@ async verifyPassword(convId: number, password: string) {
// conv.password = password
}
async muteUser(convId: number, username: string) {
async muteUser(convId: number, username: string, time: string) {
const conv = await this.findConv(convId);
console.log("MUTE USER");
conv.muted = conv.muted || [];
if (conv.muted.find(item => item === username))
return (1);
conv.muted.push(username);
this.save(conv);
setTimeout(() => {
conv.muted = conv.muted.filter((item) => item !== username)
this.save(conv);
}, 5000);
console.log("END MUTE USER");
}
async setAdmin(convId: number, username: string) {
@ -149,12 +157,14 @@ async isAdmin(convId: number, username: string) {
return (0);
}
async setPrivate(convId: number) {
async setPrivate(convId: number, bool: boolean) {
const conv = await this.findConv(convId);
if (conv.private === true)
conv.private = false;
else
conv.private = true;
console.log("bool= ", bool);
conv.private = bool;
// if (conv.private === true)
// conv.private = false;
// else
// conv.private = true;
this.save(conv);
}

View File

@ -3,10 +3,10 @@
/* ::: :::::::: */
/* config.service.ts :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* By: sadjigui <sadjigui@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/04/09 14:53:49 by apommier #+# #+# */
/* Updated: 2023/06/22 20:42:32 by apommier ### ########.fr */
/* Updated: 2023/06/24 15:09:20 by sadjigui ### ########.fr */
/* */
/* ************************************************************************** */

View File

@ -82,7 +82,7 @@ export class User {
}
@Entity()
@Entity({name: 'MatchLog' })
export class MatchLog {
@PrimaryGeneratedColumn()
id: number;

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/17 01:00:07 by apommier #+# #+# */
/* Updated: 2023/06/21 01:31:44 by apommier ### ########.fr */
/* Updated: 2023/06/24 19:29:33 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -41,6 +41,10 @@ export class UsersService {
return await this.userRepository.findOneBy({username: username});
}
async findNickname(username: string): Promise<User> {
return await this.userRepository.findOneBy({nickname: username});
}
async save(user: User): Promise<User> {
return await this.userRepository.save(user);
}
@ -71,6 +75,8 @@ export class UsersService {
user.friendRequest = user.friendRequest || [];
if (user.friendRequest.find(item => item === username))
return (1);
if (user.friends.find(item => item === username))
return (1);
user.friendRequest.push(username);
this.save(user);
return (1);
@ -96,16 +102,24 @@ export class UsersService {
async getHistory(username: string) {
const user = await this.findOne(username);
if (user) {
const children = user.children;
console.log(user);
console.log(user.children); // or perform any operations with the children
return children;
if (user)
{
// const ret = await this.matchRepository.query("SELECT * FROM \"MatchLog\" WHERE id = ($1);", [user.id]);
const ret = await this.matchRepository.query("SELECT * FROM \"MatchLog\"");
console.log("all match= ", ret);
}
// const children = user.children;
// console.log(user);
// console.log(user.children); // or perform any operations with the children
// return children;
// }
}
async addFriend(user: User, username: string) {
if (!(await this.findOne(username)))
const user2 = await this.findOne(username)
if (!user)
return (0);
// user.friendRequest = user.friendRequest || [];
user.friends = user.friends || [];
@ -117,6 +131,9 @@ export class UsersService {
}
user.friends.push(username);
user.friendRequest = user.friendRequest.filter((item) => item !== username);
user2.friends = user2.friends || [];
user2.friends.push(user.username);
this.save(user2);
this.save(user);
return (1);
}

View File

@ -6,10 +6,12 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/19 15:18:38 by apommier #+# #+# */
/* Updated: 2023/06/23 15:19:12 by apommier ### ########.fr */
/* Updated: 2023/06/24 17:20:24 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
//0.0001
import { SubscribeMessage, WebSocketGateway, OnGatewayInit, WebSocketServer, OnGatewayConnection, OnGatewayDisconnect } from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';
import { v4 as uuidv4 } from 'uuid';
@ -165,8 +167,9 @@ addMatchmaking(client: Socket, payload: any): void {
player.join(gameId);
console.log(`Player ${player.id} joined game ${gameId}`);
});
payload.gameId = gameId;
players.forEach((player) => {
player.emit('pong:gameId', gameId);
player.emit('pong:gameId', payload);
});
}
@ -200,8 +203,8 @@ joinPrivateParty(client: Socket, payload: any): void {
{
game.push(client);
const playersIds = game.map(socket => socket.id);
this.clients[playersIds[0]].emit('pong:gameId', payload.gameId);
this.clients[playersIds[1]].emit('pong:gameId', payload.gameId);
this.clients[playersIds[0]].emit('pong:gameId', payload);
this.clients[playersIds[1]].emit('pong:gameId', payload);
}
else
{
@ -337,6 +340,24 @@ addPrivateParty(client: Socket, payload: any): void {
}
}
@SubscribeMessage('pong:myPoint')
handleMyPoint(client: Socket, payload: any): void
{
const game = this.games.get(payload.gameId);
const playersIds = game.map(socket => socket.id);
console.log(`id of 0 mypoint= ${playersIds[0]}`);
if (playersIds[0] === payload.id)
{
this.clients[playersIds[1]].emit('pong:hisPoint', payload);
}
else if (playersIds[1] === payload.id)
{
this.clients[playersIds[0]].emit('pong:hisPoint', payload);
}
}
@SubscribeMessage('pong:name')
getName(client: Socket, payload: any): void
{
@ -347,11 +368,11 @@ addPrivateParty(client: Socket, payload: any): void {
if (playersIds[0] === payload.id)
{
this.clients[playersIds[1]].emit('pong:name', payload.name);
this.clients[playersIds[1]].emit('pong:name', payload);
}
if (playersIds[1] === payload.id)
{
this.clients[playersIds[0]].emit('pong:name', payload.name);
this.clients[playersIds[0]].emit('pong:name', payload);
}
}

View File

@ -1,5 +1,7 @@
REACT_APP_BASE_URL=localhost:8080
REACT_APP_BASE_URL=bess-f2r2s16:8080
REACT_APP_SOCKET_URL=bess-f2r2s16
REACT_APP_API_SECRET=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
REACT_APP_CLIENT_UID=u-s4t2ud-6d29dfa49ba7146577ffd8bf595ae8d9e5aaa3e0a9615df18777171ebf836a41
# REACT_APP_BASE_URL=92.143.191.152
# REACT_APP_BASE_URL=192.168.1.19

View File

@ -22,7 +22,7 @@
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "HOST=0.0.0.0 PORT=8001 react-scripts start",
"start": "HOST=0.0.0.0 PORT=8081 react-scripts start",
"start:dev": "npm run start --watch",
"build": "react-scripts build",
"test": "react-scripts test",

View File

@ -32,11 +32,11 @@ function RedAlert ({handleClose, text}: AlertProps) {
initial="hidden"
animate="visible"
exit="exit"
>
>
<BiErrorCircle/>
<p>{text}</p>
</motion.div>
{setTimeout(handleClose, 1500)}
{setTimeout(handleClose, 1500)}
</Backdrop>
)
}

View File

@ -40,9 +40,9 @@ function PlayButton() {
<button onClick={handleButtonClick} className="playButton">Play</button>
{/* !buttonClicked && <button onClick={handleButtonClick}>Draw on Canvas</button> */}
<div className='checkbox'>
<p><input type="checkbox" value="superpower"/> Super Power </p>
<p><input type="checkbox" value="obstacle"/> Obstacle </p>
<p><input type="checkbox" value="speed"/> Faster and Faster </p>
<p><input className="inside_checkbox" type="checkbox" value="superpower"/> Super Power </p>
<p><input className="inside_checkbox" type="checkbox" value="obstacle"/> Obstacle </p>
<p><input className="inside_checkbox" type="checkbox" value="speed"/> Faster and Faster </p>
</div>
</div>
);

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/09 08:49:24 by apommier #+# #+# */
/* Updated: 2023/06/20 13:06:35 by apommier ### ########.fr */
/* Updated: 2023/06/23 17:16:40 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -41,7 +41,7 @@ function Rank({user, index}: RankProps){
};
fetchProfilePicture();
})
}, [])
// console.log(index);
return (

View File

@ -21,18 +21,15 @@ function Ranking(){
// setFriends(tmpFriends.data);
// return tmpUser;
// console.log(`user= ${tmpUser.data.username}`);
setIsLoading(false)
setIsLoading(false);
}
catch(err){
console.log(err);
}
};
getRanking();
}, [])
console.log(`ranking after= ${ranking}`)
}, []);
console.log(`ranking after= ${ranking}`);
return (
<div>

View File

@ -27,6 +27,7 @@ import PartyInvite from "./PartyInvite.tsx";
// import {User, Conv, Message} from "../../../interfaces.tsx"
import {User, Conv} from "../../../interfaces.tsx"
import { IoLogoOctocat } from "react-icons/io5";
const TouchDiv = styled.div`
margin-left: 10px;
@ -119,7 +120,7 @@ function Chats(){
setUsers(tmpUsers.data);
// console.log(`connection....`);
socket.current = io('http://' + process.env.REACT_APP_BASE_URL + ':4001', { transports: ['polling'] });
socket.current = io('http://' + process.env.REACT_APP_SOCKET_URL + ':4001', { transports: ['polling'] });
// console.log(`connection done`);
socket.current.emit('connection', {username: tmpUser.data.username})
socket.current.on('message', (data) => { //data should be a message ?)
@ -205,11 +206,12 @@ function Chats(){
getMessage();
}, [currentChat]);
const handleSubmit = async (e: { preventDefault: () => void; })=>{
const handleSubmit = async (e: { key?: any; preventDefault: any; })=>{
e.preventDefault();
// console.log(`e= ${e.key}`)
// console.log(`name= ${user.username}`)
// let message;
console.log("in handle");
if (!user || !currentChat)
return ;
const message = {
@ -242,33 +244,11 @@ function Chats(){
}
}
const handleKeyPress = async (e: { key: string; })=> {
const handleKeyPress = async (e: { key?: any; preventDefault: () => void; })=> {
// console.log(`e in press= ${e.key}`)
if (e.key !== "Enter")
return ;
// console.log(`name= ${user.username}`)
if (!user || !currentChat)
return ;
const message = {
sender: user.username,
text: newMessages,
convId: currentChat.id,
members: null,
id: null,
};
try{
const res = await api.post('/message', message);
const convMember = await api.post('/member', message);
message.members = convMember.data.members;
message.id = res.data.id
setMessage([...messages, res.data]);
setNewMessage("");
if (socket.current)
socket.current.emit('sendMessage', message);
}
catch(err){
console.log(err)
}
handleSubmit(e);
}
@ -323,6 +303,7 @@ function Chats(){
const handleAddFriend = async () => {
try{
console.log("friend= ", friend);
const res = await api.post("/invite", {username: friend})
// if (res.data === 1)
// console.log("res in friend= ", res)
@ -371,6 +352,7 @@ function Chats(){
const handleOptionChange = (selectId: number, selectedOption: string) => {
console.log("selected Option=", selectedOption)
setFriend(selectedOption);
setSelectTag((prevTags) =>
prevTags.map((tag) =>
tag.id === selectId ? { ...tag, selectedOption } : tag
@ -389,12 +371,13 @@ function Chats(){
<div className="chat">
<div className='navbar'>
<img src={DefaultPic} alt="profile" className="pic"/>
{/* <img src={DefaultPic} alt="profile" className="pic"/> */}
<IoLogoOctocat className="catchat"/>
<span>
{isLoading || !user ? (
<h4>Loading...</h4>
) : (
<h4>{user.nickname}</h4>
<h2>Chat</h2>
)}
</span>
{/* <div className="end">
@ -463,7 +446,7 @@ function Chats(){
))}
<TouchDiv>
<motion.div onClick={handleAddFriend}>
<MdOutlineGroupAdd />
<MdOutlineGroupAdd className="catchat"/>
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{showAddFriendAlert && addFriend && (
@ -476,7 +459,7 @@ function Chats(){
</TouchDiv>
<TouchDiv>
<motion.div onClick={handleBlockFriend}>
<ImBlocked />
<ImBlocked className="block"/>
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{showBlockAlert && block && (
@ -571,7 +554,7 @@ function Chats(){
placeholder="What do you want to say"
onChange={(e) => setNewMessage(e.target.value)}
value={newMessages}
/>
/>
<div className="send">
<TbSend onClick={handleSubmit}></TbSend>
</div>

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/01 18:24:46 by apommier #+# #+# */
/* Updated: 2023/06/20 19:05:10 by apommier ### ########.fr */
/* Updated: 2023/06/24 16:00:48 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -42,15 +42,16 @@ function MessageMe({message, own}: MessageMeProps){
const [user, setUser] = useState<User>();
const scrollRef = useRef<HTMLDivElement>(null);
// console.log("Message eher")
useEffect(() => {
if (scrollRef.current)
{
scrollRef.current.scrollIntoView({ behavior: "smooth",})
}
scrollRef.current.scrollIntoView({ behavior: "smooth"});
}})
useEffect(() => {
const fetchProfilePicture = async () => {
try {
console.log("useEffect message")
// const user = await api.get("/profile");
const tmpSender = await api.post("/user", {username: message.sender})
const tmpConv = await api.post("/convId", {convId: message.convId})
@ -81,23 +82,37 @@ function MessageMe({message, own}: MessageMeProps){
window.location.reload();
};
// const isAllowed = async () => {
// const ret = await api.post("/allowed", {convId: message.convId});
// return ret.data;
// }
if (!user || !sender || !conv)
{
// console.log("return")
return (<></>);
}
// console.log("result includes=", conv.banned.includes(user.username))
// console.log("result includes=", conv.blocked.includes(user.username))
// const conv2: Conv = getConv();
// if (!conv)
// isAllowed().then((ret: number) => {
// if (!ret)
// {
// console.log("return not allowed");
// return ;
// }
// // Use the resolved currentConv here
// });
if (user.blocked && user.blocked.includes(message.sender))
return (<></>);
// else if (conv.banned && conv.banned.includes(user.username))
// {
// console.log("return2")
// return (<></>);
// }
// console.log("noy return")
else if (conv.banned && conv.banned.includes(user.username))
return (<></>);
else if (conv.muted && conv.muted.includes(user.username))
{
// console.log("muted00")
return (<></>);
}
// if (user.blocked.includes(message.sender))/
console.log("no return message good");
return (
<div className={own ? "meMessage" : "youMessage"} ref={scrollRef}>
<div>

View File

@ -7,6 +7,7 @@ import { GrAdd } from "react-icons/gr";
import { Link } from "react-router-dom";
import api from "../../script/axiosApi.tsx";
import React from "react";
import {User, Conv} from "../../../interfaces.tsx"
const dropIn = {
hidden:{y:"-100vh",
@ -21,16 +22,19 @@ const dropIn = {
}},
exit:{y: "100vh",
opacity: 0,},
};
const Modal = ({handleClose}) => {
interface ModalProps {
handleClose: Function,
}
const Modal = ({handleClose}: ModalProps) => {
// const [multi, setMulti] = useState(false);
const [selectTags, setSelectTag] = useState([{ id: 1, selectedOption: ''}]);
const [selectedOptionArray, setSelectedOptionArray] = useState([]);
const [users, setUsers] = useState([]);
const [user, setUser] = useState();
const [convs, setConvs] = useState([]);
const [selectedOptionArray, setSelectedOptionArray] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [user, setUser] = useState<User>();
const [convs, setConvs] = useState<Conv[]>([]);
const [channel, setChannel] = useState('');
@ -53,7 +57,7 @@ const Modal = ({handleClose}) => {
getConv();
}, []);
const handleOptionChange = (selectId, selectedOption) => {
const handleOptionChange = (selectId: number, selectedOption: string) => {
console.log("selected Option=", selectedOption)
setSelectTag((prevTags) =>
prevTags.map((tag) =>
@ -103,24 +107,27 @@ const Modal = ({handleClose}) => {
<Backdrop onClick={handleClose}>
<motion.div
onClick={(e) => e.stopPropagation()}
className="modal"
className="modalSetting"
// variant={dropIn}
initial="hidden"
animate="visible"
exit="exit"
>
{/* <p>New Conversation</p> */}
<div className="settingFirstPart2">
{selectTags.map((selectTag) => (
<div key={selectTag.id}>
<div key={selectTag.id}>
<select
value={selectTag.selectedOption}
onChange={(a) => handleOptionChange(selectTag.id, a.target.value)}
>
>
<option value="">{
selectTag.selectedOption ? selectTag.selectedOption : "Select an option"
}</option>
{users.filter((item) => !selectTags.some((tag) => tag.selectedOption === item.name)).map((item, index) => (
<option key={index} value={item.username}>
{item.username}
selectTag.selectedOption ? selectTag.selectedOption : "Select an option"
}</option>
{users.filter((item) => !selectTags.some((tag) => tag.selectedOption === item.nickname)).map((item, index) => (
<option key={index} value={item.username}>
{item.nickname}
</option>
))}
</select>
@ -132,30 +139,32 @@ const Modal = ({handleClose}) => {
<div className="div_submit">
<Link to='#' className="submit" onClick={ saveSelectedOptions}>Submit</Link>
<Link to="#" className="submit" onClick={handleClose}>Cancel</Link>
<Link to="#" className="submit" onClick={() => handleClose}>Cancel</Link>
</div>
</div>
<div className="settingSecondPart">
{convs.length > 0 && (
<select
value={channel}
onChange={(event) => setChannel(event.target.value)}
<select
value={channel}
onChange={(event) => setChannel(event.target.value)}
>
<option value="">Select an option</option>
{convs.map((conv) => (
!(!conv.group || conv.private || (conv.banned && conv.banned.includes(user.username)) || (conv.members && conv.members.includes(user.username))) && (
<option key={conv.id} value={conv.id}>
!(!conv.group || conv.private || (conv.banned && user && conv.banned.includes(user.username)) || (conv.members && user && conv.members.includes(user.username))) && (
<option key={conv.id} value={conv.id}>
{conv.name}
</option>
)
))}
))}
</select>
)}
{channel.private ? (
<input className="mdp" placeholder="password" type="text" />
):("")}
{/* {channel.private ? (
<input className="mdp" placeholder="passdddddword" type="text" />
):("")} */}
<div className="div_submit">
@ -164,6 +173,7 @@ const Modal = ({handleClose}) => {
</div>
</motion.div>
</Backdrop>
)

View File

@ -39,8 +39,10 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
const [selectTags, setSelectTag] = useState([{ id: 1, selectedOption: ''}]);
const [selectedUser, setSelectedUser] = useState("");
const [newName, setNewName] = useState("");
const [time, setTime] = useState("");
const [newPassword, setNewPassword] = useState("");
const [privateConv, setPrivateConv] = useState(false);
const [privateConv, setPrivateConv] = useState<Boolean>();
const [loading, setLoading] = useState<Boolean>(true);
const dark = () => setPrivateConv(true);
const light = () => setPrivateConv(false);
const [mute, setMute] = useState(false);
@ -53,9 +55,15 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
console.log("convid =", convId)
const getUsers = async ()=>{
try {
const currentConv = await api.post("/convId", {convId: convId});
// console.log("conv private =================== ", )
if (currentConv.data.private)
setPrivateConv(true);
const tmpUsers = await api.get("/users");
console.log("users=", tmpUsers.data);
setUsers(tmpUsers.data);
setLoading(false);
} catch(err){
console.log(err)
}
@ -63,6 +71,31 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
getUsers();
}, []);
useEffect(() => {
// Function to run when myVariable changes
const handleVariableChange = () => {
console.log('Variable changed:', privateConv);
if (privateConv === undefined)
{
console.log("return")
return ;
}
try {
if (privateConv)
api.post("/private", {convId: convId})
else
api.post("/public", {convId: convId})
} catch (err){
console.log(err);
}
};
if (!loading)
handleVariableChange();
// return () => {
// handleVariableChange();
// };
}, [privateConv]);
// const [multi, setMulti] = useState(false);
// const [selectedOptionArray, setSelectedOptionArray] = useState([]);
@ -80,30 +113,30 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
const handleCheckPass = (e: { target: { checked: boolean | ((prevState: boolean) => boolean); }; }) => {
setPassword(e.target.checked);
console.log("password??", e.target.checked)
console.log("password??", e.target.checked);
}
const handleCheckPriv = (e: { target: { checked: any; }; }) => {
// setPassword(e.target.checked);
if (e.target.checked)
{
console.log("chack true", e.target.checked)
try{
api.post("/private", {convId: convId})
} catch(err) {
console.log(err);
}
}
else
{
console.log("chack false", e.target.checked)
try{
api.post("/private", {convId: convId})
} catch(err) {
console.log(err);
}
}
}
// const handleCheckPriv = (e: { target: { checked: any; }; }) => {
// // setPassword(e.target.checked);
// if (e.target.checked)
// {
// console.log("chack true", e.target.checked)
// try{
// api.post("/private", {convId: convId})
// } catch(err) {
// console.log(err);
// }
// }
// else
// {
// console.log("chack false", e.target.checked)
// try{
// api.post("/private", {convId: convId})
// } catch(err) {
// console.log(err);
// }
// }
// }
const handleName = async (e: { key: string; })=>{
if (e.key !== "Enter")
@ -157,11 +190,15 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
handleClose();
};
const handleMute = async () => {
if (!selectedUser.length)
const handleMute = async (e: { key: string; }) => {
console.log(`e in press= ${e.key}`)
if (e.key != "Enter")
return ;
// console.log("value mute = ", e.target.value);
console.log("value mute = ", time);
try{
await api.post("/mute", {convId: convId, username: selectedUser})
await api.post("/mute", {convId: convId, username: selectedUser, time: time})
} catch(err) {
console.log(err);
}
@ -177,6 +214,17 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
handleClose();
};
const handleKeyPress = async (e: { key: string; })=> {
if (e.key !== "Enter")
return ;
try{
}
catch(err){
}
}
return (
<Backdrop onClick={handleClose}>
<motion.div
@ -198,7 +246,7 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
<p className="checkbox">Password<input type="checkbox" value="password" checked={password} onChange={handleCheckPass}/> </p>
{password || privateConv ? (
{password ? (
<input
onChange={(e) => setNewPassword(e.target.value)}
onKeyDown={handlePassword}
@ -208,7 +256,6 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
):
("")}
</div>
<div className="forName">
<input
@ -254,7 +301,14 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
</div>
{mute ? (
<input type="text" className="in_howLong" placeholder="How long ?" />
<input
onKeyDown={handleMute}
type="number"
className="in_howLong"
placeholder="How long ?"
value={time}
onChange={(e) => setTime(e.target.value)}
/>
):("")}
</motion.div>

View File

@ -1,12 +1,13 @@
import {motion} from "framer-motion"
import { AnimatePresence, motion } from "framer-motion"
// import Backdrop from "../Sidebar/Backdrop"
import {Link} from 'react-router-dom';
import { UserProfile } from "../../DataBase/DataUserProfile";
import {useState} from 'react';
import { Link } from 'react-router-dom';
// import { UserProfile } from "../../DataBase/DataUserProfile";
import { useState } from 'react';
import "../../styles/Profile.css"
import api from '../../script/axiosApi.tsx';
import React from "react";
import RedAlert from "../Alert/RedAlert.tsx";
const dropIn = {
hidden: {
@ -26,37 +27,55 @@ const dropIn = {
// )
// }
const ModalEdit = ( handleClose ) => {
const ModalEdit = (handleClose) => {
// let new_name = "";
const [nickname, setNickname] = useState("");
const [errTaken, setErrTaken] = useState(false);
const closeTaken = () => setErrTaken(false);
const [errTooShort, setErrTooShort] = useState(false);
const closeTooShort = () => setErrTooShort(false);
const handler = e =>
{
const handler = e => {
setNickname(e.target.value);
console.log("testeeeee")
const postNickname = async ()=>{
try{
await api.post("/nickname", {nickname: nickname})
// setUser(tmpUser.data);
// setIsLoading(false)
}
catch(err){
console.log(err);
}
const postNickname = async () => {
// try{
// await api.post("/nickname", {nickname: nickname})
// // setUser(tmpUser.data);
// // setIsLoading(false)
// }
// catch(err){
// console.log(err);
// }
};
postNickname();
}
const handlePostNickname = async () =>
{
console.log("nickname=" ,nickname)
try{
await api.post("/nickname", {nickname: nickname})
window.location.reload();
const handlePostNickname = async () => {
console.log("nickname=", nickname)
try {
const ret = await api.post("/nickname", { nickname: nickname });
// console.log("cest ici = ",ret);
// if (!ret)
console.log("test ret =", ret.data);
if (nickname.length < 3) {
setErrTooShort(true);
}
else if (ret.data) {
console.log("ici error = ", ret.data);
window.location.reload();
}
else {
console.log("nickname already set = ", ret.data);
setErrTaken(true);
}
// setUser(tmpUser.data);
// setIsLoading(false)
}
catch(err){
catch (err) {
console.log(err);
}
}
@ -66,23 +85,34 @@ const ModalEdit = ( handleClose ) => {
// //do nothing
// }
return (
<motion.div
className="modal"
variants={dropIn}
initial="hidden"
animate="visible"
exit="exit">
<h2>Type your new name</h2>
<input className="text" maxLength="10" type="text" value={nickname} onChange={handler} handleClose/>
<div>
<div className="button" onClick={ () => handlePostNickname()}>
change
{/* <Link className="button" to={""}>change</Link> */}
</div>
<motion.div
className="modal"
variants={dropIn}
initial="hidden"
animate="visible"
exit="exit">
<h2>Type your new name</h2>
<input className="text" minLength={2} maxLength={10} type="text" value={nickname} onChange={handler} />
<div>
<div className="button" onClick={handlePostNickname}>
change
{/* <Link className="button" to={""}>change</Link> */}
</div>
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{
errTaken ? (
<RedAlert handleClose={closeTaken} text="Error: Nickname already taken" />
) : ("")
}
{
errTooShort ? (
<RedAlert handleClose={closeTooShort} text="Error: Nickname it too short" />
) : ("")
}
</AnimatePresence>
</div>
</motion.div>
)
}

View File

@ -94,7 +94,7 @@ function WinLoss() {
// <span>Loading...</span>
) : (
<div className='scroll'>
<h2 className='title'>Match history Win/Loss</h2>
<h2 className='title'>Match history {user.win}/{user.loss}</h2>
{history.map((c: Matchlog, index) => {
return (
<div key={index} className='elements'>

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/09 08:18:58 by apommier #+# #+# */
/* Updated: 2023/06/20 13:41:44 by apommier ### ########.fr */
/* Updated: 2023/06/23 17:12:07 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -59,9 +59,8 @@ export default function Friend({currentUser}: UserProps)
console.error('Error fetching profile picture:', error);
}
};
fetchProfilePicture();
})
}, []);
function getStatus(friend: User)
{

View File

@ -15,7 +15,7 @@ const UserChat = styled.div `
gap: 5px;
color: white;
cursor: pointer;
margin-top: 10px;
&:hover{
background-color: #3e3c61;
}
@ -101,13 +101,14 @@ export default function Friend({currentUser}: UserProps)
<img className="pic-user" src={DefaultPicture} alt="Default Profile Picture" />
)}
{request ? (
<div className="infoSideBar">
<div className="end">
<span onClick={() => handleButtonClick(currentUser)}>{currentUser.nickname}</span>
<RxCheckCircled onClick={() => Accept(request)} color={'green'}/>
<RxCircleBackslash onClick={() => Refuse(request)} color={'red'}/>
<div className="end">
<RxCheckCircled className="friendRequest" onClick={() => Accept(request)} color={'green'}/>
<RxCircleBackslash className="friendRequest" onClick={() => Refuse(request)} color={'red'}/>
</div>
</div>
) : ( "" )}
</UserChat>
)
}

View File

@ -7,6 +7,7 @@ import styled from "styled-components";
import Friend from './Friend.tsx';
import FriendRequest from './FriendRequest.tsx';
import {IoMdPeople} from 'react-icons/io'
import { ImBlocked } from 'react-icons/im';
import { MdOutlineGroupAdd } from 'react-icons/md';
import {User} from "../../../interfaces.tsx"
@ -88,26 +89,15 @@ function Social (){
<div>
<div className='navbar'>
{/* <img src={DefaultPic} alt="profile" className="pic"/> */}
{profilePicture ? (
<img className="pic" src={`data:image/jpeg;base64,${profilePicture}`} />
) : (
<img className="pic" src={DefaultPicture} alt="Default Profile Picture" />
)}
<IoMdPeople className="catchat"/>
<span>
{isLoading || !user ? (
<h4>Loading...</h4>
) : (
<h4>{user.nickname}</h4>
<h2>Social</h2>
)}
</span>
<div className="end">
<TouchDiv>
<MdOutlineGroupAdd/>
</TouchDiv>
<TouchDiv>
<ImBlocked/>
</TouchDiv>
</div>
</div>
{/* map with fiend request */}
@ -119,6 +109,7 @@ function Social (){
{friends.map(c=> (
<Friend currentUser={c}/>
))}
</div>
)
}

View File

@ -3,10 +3,10 @@
/* ::: :::::::: */
/* Home.tsx :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* By: sadjigui <sadjigui@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* Created: 2023/06/09 08:19:04 by apommier #+# #+# */
/* Updated: 2023/06/23 15:58:14 by apommier ### ########.fr */
/* Updated: 2023/06/23 22:11:28 by apommier ### ########.fr */
/* */
/* ************************************************************************** */
@ -20,7 +20,7 @@ import { motion, AnimatePresence } from 'framer-motion'
// import { GrClose } from 'react-icons/gr'
import { Link } from "react-router-dom";
import ModalEdit from "../components/Profile/EditName.tsx";
import {AiOutlineHistory} from 'react-icons/ai'
import {AiOutlineCloseCircle, AiOutlineHistory} from 'react-icons/ai'
import { MdQrCodeScanner, MdOutlinePhotoLibrary } from 'react-icons/md';
import { GiWingedSword, GiCrownedSkull } from 'react-icons/gi';
@ -82,43 +82,13 @@ function Profile () {
// }
};
// const handleUpload = async () => {
// const formData = new FormData();
// formData.append('photo', selectedPhoto);
// try {
// await api.post('/picture', formData);
// console.log('File uploaded successfully');
// window.location.reload();
// } catch (error) {
// console.error('Error uploading file:', error);
// }
// };
// const handleUpload = async (event: React.FormEvent) => {
// event.preventDefault()
// console.log("up photo")
// if (selectedPhoto) {
// console.log("selected photo")
// const formData = new FormData();
// formData.append('photo', selectedPhoto);
// try {
// await api.post('/picture', formData);
// console.log('File uploaded successfully');
// window.location.reload();
// } catch (error) {
// console.error('Error uploading file:', error);
// }
// } else {
// console.log('No file selected');
// }
// };
useEffect(()=> {
const getUser = async ()=>{
console.log(`username= ${username}`)
// const pic
let pic
let pic;
try{
console.log("before request")
const me = await api.get("/profile")
if (!username)
{
@ -160,7 +130,7 @@ function Profile () {
{isLoading || !user ? (
<h1>Loading...</h1>
) : (
<h1>{user.nickname}</h1>
<h1 className='user_name'>{user.nickname}</h1>
)}
</span>
@ -202,7 +172,7 @@ function Profile () {
function Home () {
const [move, setmove ] = useState(false);
const [user, setUser] = useState([]);
const [user, setUser] = useState<User>();
const [successQr, setSuccessQr] = useState(false);
const [successSword, setSuccessSword] = useState(false);
@ -211,12 +181,23 @@ function Home () {
const closeSword = () => setSuccessSword(false);
const closeCrown = () => setSuccessCrown(false);
const { username } = useParams();
useEffect(() => {
const fetchSuccess = async () => {
try {
const tmpUser = await api.get("/profile");
setUser(tmpUser.data);
if (!username)
{
const tmpUser = await api.get("/profile");
setUser(tmpUser.data);
}
else
{
const tmpUser = await api.post("/user", {username: username});
setUser(tmpUser.data);
}
// const tmpUser = await api.get("/profile");
// setUser(tmpUser.data);
}
catch (error)
{
@ -224,7 +205,7 @@ function Home () {
}
};
fetchSuccess();
})
}, []);
return (
<motion.div className="page"
@ -232,14 +213,13 @@ function Home () {
animate={{opacity: 1}}
exit={{opacity: -1}}>
<div>
{user.otp_verified ? (
{user && user.otp_verified ? (
<MdQrCodeScanner className='success' onClick={() => setSuccessQr(true)}/>
):("")}
{user.win >= 2 ? (
{user && user.win >= 2 ? (
<GiWingedSword className="success" onClick={() => setSuccessSword(true)}/>
):("")}
{user.win >= 5 ? (
{user && user.win >= 5 ? (
<GiCrownedSkull className="success" onClick={() => setSuccessCrown(true)}/>
):("")}
</div>
@ -256,7 +236,7 @@ function Home () {
className="div_history"
// className="history"
onClick={ () => setmove(!move)}>
<Link to="#" className="history"><AiOutlineHistory/> Match History</Link>
<Link to="#" className="history"> {move ? (<AiOutlineCloseCircle/>):(<AiOutlineHistory/>)} Match History</Link>
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{successQr ? (

View File

@ -148,7 +148,7 @@ function QrCode () {
<h1>Double Auth Validation</h1>
<input
onKeyDown={handleKeyPress}
type="text"
type="number"
className="qr"
placeholder="6 Digits Code"
value={code}
@ -156,7 +156,7 @@ function QrCode () {
/>
</>
) : (
<button onClick={handleDesactivate}>Desactivate 2FA</button>
<button className="desactivate" onClick={handleDesactivate}>Desactivate 2FA</button>
)}
</>

View File

@ -61,7 +61,7 @@ function DrawCanvas(option: number, gameParam: GameProps) {
if(!ctx)
return ;
const socket = io('http://localhost:4000', { transports: ['polling'] });
const socket = io('http://' + process.env.REACT_APP_SOCKET_URL + ':4000', { transports: ['polling'] });
// useEffect(() => {
// console.log("useeffect?????????????????")
// return () => {
@ -170,7 +170,9 @@ socket.on('pong:privateId', async (data) => {
socket.on('pong:gameId', async (data) => {
console.log("gameId received");
gameId = data;
gameId = data.gameId;
console.log("gameid = ", gameId);
console.log("data gameid = ", data);
try {
let response = await api.get('/profile');
@ -190,6 +192,16 @@ socket.on('pong:gameId', async (data) => {
console.log("emit to name");
socket.emit('pong:name', info);
if (data.id === myId)
{
console.log("myId= true")
vX = 0.0001;
}
else
{
console.log("myId= false")
vX = -0.0001;
}
} catch (error) {
console.log(error);
// Handle error here
@ -198,7 +210,11 @@ socket.on('pong:gameId', async (data) => {
});
socket.on('pong:name', (data) => {
opName = data;
opName = data.name;
// if (data.myId === myId)
// vX = 0.0001;
// else
// vX = -0.0001;
console.log(`opponent Name= ${opName}`)
});
@ -222,7 +238,6 @@ socket.on('pong:info', (data) => {
vY = data.vY;
});
socket.on('pong:paddle', (data) => {
console.log("paddle info receive")
oPaddleY = (data.paddleY / data.height) * canvas.height
@ -251,12 +266,27 @@ socket.on('pong:point', (data) => {
// console.log("up point");
myScore = data.point;
// }
vX = 0;
vX = -0.0001;
vY = 0;
ballX = canvas.width / 2;
ballY = canvas.height / 2;
});
socket.on('pong:hisPoint', (data) => {
// hisScore += 1;
console.log("myPointawdawdawdawd point");
// if (vX != 0)
// {
// console.log("up point");
hisScore = data.point;
// }
vX = -0.0001;
vY = 0;
ballX = canvas.width / 2;
ballY = canvas.height / 2;
// send_forced_info();
});
//========================================================================================================
//========================================================================================================
// Socket EMIT
@ -323,6 +353,26 @@ socket.on('pong:point', (data) => {
point: hisScore,
}
socket.emit('pong:point', info);
vX = 0.0001;
}
function send_my_point()
{
if (!gameId || !canvas)
return ;
// console.log("send point");
const info = {
id: myId,
gameId: gameId,
point: myScore,
}
socket.emit('pong:myPoint', info);
myScore++;
vX = 0.0001;
vY = 0;
ballX = canvas.width / 2;
ballY = canvas.height / 2;
send_forced_info();
}
function send_paddle_info()
@ -459,7 +509,10 @@ async function draw(timestamp: number)
{
console.log("turning, running= ", running);
if (!running)
{
window.location.replace("http://" + process.env.REACT_APP_BASE_URL + "/pong")
return ;
}
if (!gameId || !canvas )
{
// console.log("nogameid score= ", myScore);
@ -593,16 +646,17 @@ async function draw(timestamp: number)
}
ballX = canvas.width / 2;
ballY = canvas.height / 2;
vX = 0;
vX = 0.0001;
vY = 0;
hisScore += 1;
send_point();
// send_forced_info();
}
if (ballX > canvas.width)
if (ballX > (canvas.width * 1.2) && ballX - vX > canvas.width)
{
console.log("ball out win point pls")
send_my_point();
// if (ballX > canvas.width * 2)
// socket.emit
// console.log("win point")
// if (ballY <= paddleY + paddleHeight + ballRadius && ballY >= paddleY - ballRadius)
// {
@ -702,9 +756,8 @@ async function draw(timestamp: number)
vX -= 0.0001;
}
send_forced_info();
// console.log(`vx = ${vX}`);
}
else if (event.code === "KeyR")
else if (event.code === "KeyW")
{
if (!superpowerModifier)
return ;
@ -717,6 +770,13 @@ async function draw(timestamp: number)
paddleY = canvas.height / 2 - paddleHeight / 2;
console.log('Cinq secondes se sont écoulées.');
}, 5000);
// setTimeout(() => {
// // code à exécuter après 5 secondes
// paddleHeight = canvas.height * 0.25;
// paddleY = canvas.height / 2 - paddleHeight / 2;
// console.log('Cinq secondes se sont écoulées.');
// }, 5000);
}
});

View File

@ -3,12 +3,15 @@ import { useState, useEffect } from 'react'
import queryString from 'query-string';
import api from "./axiosApi.tsx";
import axios from 'axios';
import React from 'react';
import {Matchlog, User} from "../../interfaces.tsx"
function SuccessToken() {
const location = useLocation();
const { data } = queryString.parse(location.search);
const [code, setCode] = useState('');
const [user, setUser] = useState(false);
const [user, setUser] = useState<User>();
useEffect(() => {
if (!data) {
@ -37,7 +40,7 @@ function SuccessToken() {
getUser();
}, [data]);
const handleKeyPress = async (e)=>{
const handleKeyPress = async (e: { key: string; })=>{
// console.log(`e in press= ${e.key}`)
if (e.key !== "Enter")
return ;
@ -90,7 +93,8 @@ function SuccessToken() {
// Render a loading indicator or return null while user is being fetched
return <h1>Loading...</h1>;
}
if (!data)
return ;
const cleanData = data.slice(1, -1); // Declare cleanData here as well
if (!user.otp_verified) {

View File

@ -5,15 +5,30 @@
background-color: black;
height: 100%;
}
input.qr::-webkit-outer-spin-button,
input.qr::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input.qr{
width: 20%;
width: auto;
border-radius: 5px;
background-color: rgb(0, 0, 0);
margin : 1%;
color:white;
color:rgb(42, 41, 41);
border-style: solid;
border-width: 1px;
}
.desactivate {
margin: 40vh;
color: ghostwhite;
outline: 0;
border-radius: 20px;
padding: 20px;
background-image: linear-gradient(90deg, #5843e4, #5a0760);
border: 0;
}
.App-logo {
height: 40vmin;
pointer-events: none;
@ -48,3 +63,9 @@ input.qr{
transform: rotate(360deg);
}
}
.friendRequest {
margin-left: 4vh;
stroke-width: 0.5;
font-size: x-large;
}

View File

@ -3,6 +3,7 @@
margin: 50px;
}
.rank_elements {
border-width:1px;
border-style:solid;
@ -18,11 +19,23 @@
/* background-color: #5843e4; */
/* border-color: white; */
overflow: scroll;
height: 70vh;
height: 68vh;
}
.profilePic{
margin-left: 10px;
/* margin-top: 10px; */
height: 30px;
width: 30px;
border-radius: 50%;
}
@media screen and (max-width: 755px){
.game{
display: grid;
height: 20vh;
}
.scroll{
height: 20vh;
}
}

View File

@ -1,6 +1,6 @@
.home{
background-color: rgb(0, 0, 0);
height: 90vh;
height: 70vh;
display: flex;
align-items: center;
justify-content: center;
@ -19,7 +19,8 @@ select{
border: 0!important;
margin: 5px;
font-size: 18px;
border-radius: 6px;
padding: 5px;
border-radius: 1000px;
}
.modal{
@ -54,6 +55,7 @@ select{
height: 74vh;
width: 30%;
overflow: scroll;
border-radius: 0px 0px 0px 10px;
/* width: 2rem; */
/* height: 4rem; */
}
@ -131,16 +133,17 @@ select{
}
.messages{
background-color: rgb(26, 26, 26);
/* background-color: rgb(26, 26, 26); */
/* height: calc(100% - 118px); */
width: 70%;
/* height: 300px; */
border-radius: 0px 0px 10px 0px;
overflow: scroll;
}
.input{
display: flex;
height: 50px;
height: 6vh;
background-color: white;
color:#060b26;
border: none;
@ -202,6 +205,7 @@ p {
text-decoration: none;
font-weight:lighter;
margin: 1%;
height: 25px;
}
.darkSubmit{
@ -307,11 +311,17 @@ p {
/* flex-direction: column; */
/* align-items: center; */
background-color: #3e3c61;
overflow: scroll;
}
.settingFirstPart{
margin-top: 10%;
margin-left: 15%;
margin-left: 20%;
}
.settingFirstPart2{
margin-top: 10%;
margin-left: 30%;
}
.settingSecondPart{
@ -324,6 +334,7 @@ p {
.checkbox{
display:flex;
flex-direction:row;
margin-left: 60px;
}
input.in{
@ -331,17 +342,25 @@ input.in{
margin-left: 0px;
background-color: black;
color: white;
border-radius: 12px;
border-radius: 4px;
width: 70%;
height: 100%;
font-weight:100;
font-size: 20px;
padding: 7px;
}
input.in_howLong{
margin-top: 14.5%;
margin-top: 13%;
margin-left: 0px;
background-color: black;
color: white;
border-radius: 12px;
width: 15%;
border-radius: 4px;
width: 10%;
height: 10%;
font-weight:100;
font-size: 20px;
padding: 7px;
}
.mdp{
@ -351,3 +370,18 @@ input.in_howLong{
width: 20%;
}
.case{
height: auto;
width: auto;
margin-left: 10px;
}
.catchat{
font-size: 30px;
margin-left: 12px;
}
.block{
font-size: 25px;
margin-left: 12px;
}

View File

@ -62,6 +62,7 @@
.page {
text-align: center;
overflow-y: scroll;
/* height: 80vh; */
/* height: 50vh; */
/* width: 50vh; */
/* background-color: black; */
@ -80,9 +81,9 @@
border-radius: 50%;
border: thick;
border-color: red;
margin-left: 20px;
/* border-image: linear-gradient(90deg, #5843e4, #5a0760); */
/* margin-top: 20px; */
}
.home{
@ -96,11 +97,11 @@
}
.history{
display: inline-block;
display:inline-block;
color: white;
background-color: #5843e4;
border-radius: 20px;
padding: 14px;
padding: 1.3% 30%;
font-size: 1.7rem;
text-decoration: none;
font-weight: bold;
@ -153,6 +154,13 @@
text-decoration: none;
font-weight: bold;
}
.user_name{
/* background-image: linear-gradient(90deg, #5843e4, #5a0760); */
background: -webkit-linear-gradient(60deg, #5843e4, #5a0760);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
/* canvas {
margin-top: 20px;
border: solid 0px #ccc;

View File

@ -1,20 +1,20 @@
.playButton {
background-image: linear-gradient(90deg, #5843e4, #5a0760);
display: flex;
flex-wrap: wrap;
overflow: hidden;
border-radius: 5vh;
color: white;
/* display: block; */
display: block;
margin: auto;
margin-top: 30vh;
padding: 2vh 4vw;
padding: 2vh 5vw;
height: 10vh;
width: 20vw;
font-size: 250%;
text-align: center;
font-size: 300%;
}
.inside_checkbox{
height : 70%;
width: 70%;
}
.field {
background-color: rgb(249, 249, 249);
@ -69,6 +69,18 @@
}
}
.responsive{
display: flex;
flex-direction: column;
}
/* @media screen and (max-width: 350px){
.responsive{
display:list-item;
flex-direction: column;
}
} */
#myCanvas {
background-color: rgb(124, 47, 47);
/* position: absolute; */

View File

@ -14,19 +14,9 @@ services:
- 8080:8080
volumes:
- ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
# volumes:
# - "./conf:/etc/nginx/templates/"
# ports:
# - 80:80
# volumes:
# - ./conf/nginx.conf:/etc/nginx/conf.d/default.conf
# command: sh -c "envsubst < /etc/nginx/conf.d/default.conf > /etc/nginx/conf.d/default.conf && nginx -g 'daemon off;'"
# - ./containers/frontend:/var/www/html
networks:
- pongNetwork
react_app:
image: node:latest
container_name: react_app