Compare commits

..

1 Commits

36 changed files with 500 additions and 601 deletions

11
.env
View File

@ -12,9 +12,14 @@
#URL #URL
NGINX_ENVSUBST_TEMPLATE_SUFFIX=".conf" NGINX_ENVSUBST_TEMPLATE_SUFFIX=".conf"
BASE_URL=bess-f2r2s16:8080
REACT_APP_BASE_URL=bess-f2r2s16:8080 # BASE_URL=http://localhost
REDIRECT_URI=http://bess-f2r2s16:8080/api/auth/login 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
POSTGRES_HOST=postgresql POSTGRES_HOST=postgresql
POSTGRES_USER=postgres POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres POSTGRES_PASSWORD=postgres

3
.gitignore vendored
View File

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

153
README.md Normal file
View File

@ -0,0 +1,153 @@
# 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 'postgres'; ALTER USER postgres WITH PASSWORD 'pass';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,12 @@
import { AnimatePresence, motion } from "framer-motion" import {motion} from "framer-motion"
// import Backdrop from "../Sidebar/Backdrop" // import Backdrop from "../Sidebar/Backdrop"
import { Link } from 'react-router-dom'; import {Link} from 'react-router-dom';
// import { UserProfile } from "../../DataBase/DataUserProfile"; import { UserProfile } from "../../DataBase/DataUserProfile";
import { useState } from 'react'; import {useState} from 'react';
import "../../styles/Profile.css" import "../../styles/Profile.css"
import api from '../../script/axiosApi.tsx'; import api from '../../script/axiosApi.tsx';
import React from "react"; import React from "react";
import RedAlert from "../Alert/RedAlert.tsx";
const dropIn = { const dropIn = {
hidden: { hidden: {
@ -27,55 +26,37 @@ const dropIn = {
// ) // )
// } // }
const ModalEdit = (handleClose) => { const ModalEdit = ( handleClose ) => {
// let new_name = ""; // let new_name = "";
const [nickname, setNickname] = useState(""); 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); setNickname(e.target.value);
console.log("testeeeee") console.log("testeeeee")
const postNickname = async () => { const postNickname = async ()=>{
// try{ try{
// await api.post("/nickname", {nickname: nickname}) await api.post("/nickname", {nickname: nickname})
// // setUser(tmpUser.data); // setUser(tmpUser.data);
// // setIsLoading(false) // setIsLoading(false)
// } }
// catch(err){ catch(err){
// console.log(err); console.log(err);
// } }
}; };
postNickname(); postNickname();
} }
const handlePostNickname = async () => { const handlePostNickname = async () =>
console.log("nickname=", nickname) {
try { console.log("nickname=" ,nickname)
const ret = await api.post("/nickname", { nickname: nickname }); try{
// console.log("cest ici = ",ret); await api.post("/nickname", {nickname: nickname})
// if (!ret) window.location.reload();
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); // setUser(tmpUser.data);
// setIsLoading(false) // setIsLoading(false)
} }
catch (err) { catch(err){
console.log(err); console.log(err);
} }
} }
@ -85,34 +66,23 @@ const ModalEdit = (handleClose) => {
// //do nothing // //do nothing
// } // }
return ( return (
<motion.div <motion.div
className="modal" className="modal"
variants={dropIn} variants={dropIn}
initial="hidden" initial="hidden"
animate="visible" animate="visible"
exit="exit"> exit="exit">
<h2>Type your new name</h2> <h2>Type your new name</h2>
<input className="text" minLength={2} maxLength={10} type="text" value={nickname} onChange={handler} /> <input className="text" maxLength="10" type="text" value={nickname} onChange={handler} handleClose/>
<div> <div>
<div className="button" onClick={handlePostNickname}> <div className="button" onClick={ () => handlePostNickname()}>
change change
{/* <Link className="button" to={""}>change</Link> */} {/* <Link className="button" to={""}>change</Link> */}
</div>
</div> </div>
<AnimatePresence initial={false} onExitComplete={() => null}> </motion.div>
{
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> // <span>Loading...</span>
) : ( ) : (
<div className='scroll'> <div className='scroll'>
<h2 className='title'>Match history {user.win}/{user.loss}</h2> <h2 className='title'>Match history Win/Loss</h2>
{history.map((c: Matchlog, index) => { {history.map((c: Matchlog, index) => {
return ( return (
<div key={index} className='elements'> <div key={index} className='elements'>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -61,7 +61,7 @@ function DrawCanvas(option: number, gameParam: GameProps) {
if(!ctx) if(!ctx)
return ; return ;
const socket = io('http://' + process.env.REACT_APP_SOCKET_URL + ':4000', { transports: ['polling'] }); const socket = io('http://localhost:4000', { transports: ['polling'] });
// useEffect(() => { // useEffect(() => {
// console.log("useeffect?????????????????") // console.log("useeffect?????????????????")
// return () => { // return () => {
@ -170,9 +170,7 @@ socket.on('pong:privateId', async (data) => {
socket.on('pong:gameId', async (data) => { socket.on('pong:gameId', async (data) => {
console.log("gameId received"); console.log("gameId received");
gameId = data.gameId; gameId = data;
console.log("gameid = ", gameId);
console.log("data gameid = ", data);
try { try {
let response = await api.get('/profile'); let response = await api.get('/profile');
@ -192,16 +190,6 @@ socket.on('pong:gameId', async (data) => {
console.log("emit to name"); console.log("emit to name");
socket.emit('pong:name', info); 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) { } catch (error) {
console.log(error); console.log(error);
// Handle error here // Handle error here
@ -210,11 +198,7 @@ socket.on('pong:gameId', async (data) => {
}); });
socket.on('pong:name', (data) => { socket.on('pong:name', (data) => {
opName = data.name; opName = data;
// if (data.myId === myId)
// vX = 0.0001;
// else
// vX = -0.0001;
console.log(`opponent Name= ${opName}`) console.log(`opponent Name= ${opName}`)
}); });
@ -238,6 +222,7 @@ socket.on('pong:info', (data) => {
vY = data.vY; vY = data.vY;
}); });
socket.on('pong:paddle', (data) => { socket.on('pong:paddle', (data) => {
console.log("paddle info receive") console.log("paddle info receive")
oPaddleY = (data.paddleY / data.height) * canvas.height oPaddleY = (data.paddleY / data.height) * canvas.height
@ -266,27 +251,12 @@ socket.on('pong:point', (data) => {
// console.log("up point"); // console.log("up point");
myScore = data.point; myScore = data.point;
// } // }
vX = -0.0001; vX = 0;
vY = 0; vY = 0;
ballX = canvas.width / 2; ballX = canvas.width / 2;
ballY = canvas.height / 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 // Socket EMIT
@ -353,26 +323,6 @@ socket.on('pong:hisPoint', (data) => {
point: hisScore, point: hisScore,
} }
socket.emit('pong:point', info); 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() function send_paddle_info()
@ -509,10 +459,7 @@ async function draw(timestamp: number)
{ {
console.log("turning, running= ", running); console.log("turning, running= ", running);
if (!running) if (!running)
{
window.location.replace("http://" + process.env.REACT_APP_BASE_URL + "/pong")
return ; return ;
}
if (!gameId || !canvas ) if (!gameId || !canvas )
{ {
// console.log("nogameid score= ", myScore); // console.log("nogameid score= ", myScore);
@ -646,17 +593,16 @@ async function draw(timestamp: number)
} }
ballX = canvas.width / 2; ballX = canvas.width / 2;
ballY = canvas.height / 2; ballY = canvas.height / 2;
vX = 0.0001; vX = 0;
vY = 0; vY = 0;
hisScore += 1; hisScore += 1;
send_point(); send_point();
// send_forced_info(); // send_forced_info();
} }
if (ballX > (canvas.width * 1.2) && ballX - vX > canvas.width) if (ballX > canvas.width)
{ {
console.log("ball out win point pls")
send_my_point();
// if (ballX > canvas.width * 2) // if (ballX > canvas.width * 2)
// socket.emit
// console.log("win point") // console.log("win point")
// if (ballY <= paddleY + paddleHeight + ballRadius && ballY >= paddleY - ballRadius) // if (ballY <= paddleY + paddleHeight + ballRadius && ballY >= paddleY - ballRadius)
// { // {
@ -756,8 +702,9 @@ async function draw(timestamp: number)
vX -= 0.0001; vX -= 0.0001;
} }
send_forced_info(); send_forced_info();
// console.log(`vx = ${vX}`);
} }
else if (event.code === "KeyW") else if (event.code === "KeyR")
{ {
if (!superpowerModifier) if (!superpowerModifier)
return ; return ;
@ -770,13 +717,6 @@ async function draw(timestamp: number)
paddleY = canvas.height / 2 - paddleHeight / 2; paddleY = canvas.height / 2 - paddleHeight / 2;
console.log('Cinq secondes se sont écoulées.'); console.log('Cinq secondes se sont écoulées.');
}, 5000); }, 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,15 +3,12 @@ import { useState, useEffect } from 'react'
import queryString from 'query-string'; import queryString from 'query-string';
import api from "./axiosApi.tsx"; import api from "./axiosApi.tsx";
import axios from 'axios'; import axios from 'axios';
import React from 'react';
import {Matchlog, User} from "../../interfaces.tsx"
function SuccessToken() { function SuccessToken() {
const location = useLocation(); const location = useLocation();
const { data } = queryString.parse(location.search); const { data } = queryString.parse(location.search);
const [code, setCode] = useState(''); const [code, setCode] = useState('');
const [user, setUser] = useState<User>(); const [user, setUser] = useState(false);
useEffect(() => { useEffect(() => {
if (!data) { if (!data) {
@ -40,7 +37,7 @@ function SuccessToken() {
getUser(); getUser();
}, [data]); }, [data]);
const handleKeyPress = async (e: { key: string; })=>{ const handleKeyPress = async (e)=>{
// console.log(`e in press= ${e.key}`) // console.log(`e in press= ${e.key}`)
if (e.key !== "Enter") if (e.key !== "Enter")
return ; return ;
@ -93,8 +90,7 @@ function SuccessToken() {
// Render a loading indicator or return null while user is being fetched // Render a loading indicator or return null while user is being fetched
return <h1>Loading...</h1>; return <h1>Loading...</h1>;
} }
if (!data)
return ;
const cleanData = data.slice(1, -1); // Declare cleanData here as well const cleanData = data.slice(1, -1); // Declare cleanData here as well
if (!user.otp_verified) { if (!user.otp_verified) {

View File

@ -5,30 +5,15 @@
background-color: black; background-color: black;
height: 100%; height: 100%;
} }
input.qr::-webkit-outer-spin-button,
input.qr::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
input.qr{ input.qr{
width: auto; width: 20%;
border-radius: 5px; border-radius: 5px;
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
margin : 1%; margin : 1%;
color:rgb(42, 41, 41); color:white;
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 { .App-logo {
height: 40vmin; height: 40vmin;
pointer-events: none; pointer-events: none;
@ -63,9 +48,3 @@ input.qr{
transform: rotate(360deg); transform: rotate(360deg);
} }
} }
.friendRequest {
margin-left: 4vh;
stroke-width: 0.5;
font-size: x-large;
}

View File

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

View File

@ -1,6 +1,6 @@
.home{ .home{
background-color: rgb(0, 0, 0); background-color: rgb(0, 0, 0);
height: 70vh; height: 90vh;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -19,8 +19,7 @@ select{
border: 0!important; border: 0!important;
margin: 5px; margin: 5px;
font-size: 18px; font-size: 18px;
padding: 5px; border-radius: 6px;
border-radius: 1000px;
} }
.modal{ .modal{
@ -55,7 +54,6 @@ select{
height: 74vh; height: 74vh;
width: 30%; width: 30%;
overflow: scroll; overflow: scroll;
border-radius: 0px 0px 0px 10px;
/* width: 2rem; */ /* width: 2rem; */
/* height: 4rem; */ /* height: 4rem; */
} }
@ -133,17 +131,16 @@ select{
} }
.messages{ .messages{
/* background-color: rgb(26, 26, 26); */ background-color: rgb(26, 26, 26);
/* height: calc(100% - 118px); */ /* height: calc(100% - 118px); */
width: 70%; width: 70%;
/* height: 300px; */ /* height: 300px; */
border-radius: 0px 0px 10px 0px;
overflow: scroll; overflow: scroll;
} }
.input{ .input{
display: flex; display: flex;
height: 6vh; height: 50px;
background-color: white; background-color: white;
color:#060b26; color:#060b26;
border: none; border: none;
@ -205,7 +202,6 @@ p {
text-decoration: none; text-decoration: none;
font-weight:lighter; font-weight:lighter;
margin: 1%; margin: 1%;
height: 25px;
} }
.darkSubmit{ .darkSubmit{
@ -311,17 +307,11 @@ p {
/* flex-direction: column; */ /* flex-direction: column; */
/* align-items: center; */ /* align-items: center; */
background-color: #3e3c61; background-color: #3e3c61;
overflow: scroll;
} }
.settingFirstPart{ .settingFirstPart{
margin-top: 10%; margin-top: 10%;
margin-left: 20%; margin-left: 15%;
}
.settingFirstPart2{
margin-top: 10%;
margin-left: 30%;
} }
.settingSecondPart{ .settingSecondPart{
@ -334,7 +324,6 @@ p {
.checkbox{ .checkbox{
display:flex; display:flex;
flex-direction:row; flex-direction:row;
margin-left: 60px;
} }
input.in{ input.in{
@ -342,25 +331,17 @@ input.in{
margin-left: 0px; margin-left: 0px;
background-color: black; background-color: black;
color: white; color: white;
border-radius: 4px; border-radius: 12px;
width: 70%; width: 70%;
height: 100%;
font-weight:100;
font-size: 20px;
padding: 7px;
} }
input.in_howLong{ input.in_howLong{
margin-top: 13%; margin-top: 14.5%;
margin-left: 0px; margin-left: 0px;
background-color: black; background-color: black;
color: white; color: white;
border-radius: 4px; border-radius: 12px;
width: 10%; width: 15%;
height: 10%;
font-weight:100;
font-size: 20px;
padding: 7px;
} }
.mdp{ .mdp{
@ -370,18 +351,3 @@ input.in_howLong{
width: 20%; 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,7 +62,6 @@
.page { .page {
text-align: center; text-align: center;
overflow-y: scroll; overflow-y: scroll;
/* height: 80vh; */
/* height: 50vh; */ /* height: 50vh; */
/* width: 50vh; */ /* width: 50vh; */
/* background-color: black; */ /* background-color: black; */
@ -81,9 +80,9 @@
border-radius: 50%; border-radius: 50%;
border: thick; border: thick;
border-color: red; border-color: red;
margin-left: 20px;
/* border-image: linear-gradient(90deg, #5843e4, #5a0760); */ /* border-image: linear-gradient(90deg, #5843e4, #5a0760); */
/* margin-top: 20px; */
} }
.home{ .home{
@ -97,11 +96,11 @@
} }
.history{ .history{
display:inline-block; display: inline-block;
color: white; color: white;
background-color: #5843e4; background-color: #5843e4;
border-radius: 20px; border-radius: 20px;
padding: 1.3% 30%; padding: 14px;
font-size: 1.7rem; font-size: 1.7rem;
text-decoration: none; text-decoration: none;
font-weight: bold; font-weight: bold;
@ -154,13 +153,6 @@
text-decoration: none; text-decoration: none;
font-weight: bold; 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 { /* canvas {
margin-top: 20px; margin-top: 20px;
border: solid 0px #ccc; border: solid 0px #ccc;

View File

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

View File

@ -14,9 +14,19 @@ services:
- 8080:8080 - 8080:8080
volumes: volumes:
- ./conf/nginx.conf:/etc/nginx/conf.d/default.conf - ./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: networks:
- pongNetwork - pongNetwork
react_app: react_app:
image: node:latest image: node:latest
container_name: react_app container_name: react_app