Compare commits

..

1 Commits

36 changed files with 500 additions and 601 deletions

11
.env
View File

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

3
.gitignore vendored
View File

@ -1,5 +1,4 @@
.env
containers/react/.env
#.env
backend/node_modules/
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-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass http://react_app:8081;
proxy_pass http://react_app:8001;
}
location /api {
@ -21,7 +21,7 @@ server {
proxy_pass http://api:3000/api;
}
location /socket.io {
location /socket {
# 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/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
// 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);
@ -483,6 +480,8 @@ export class AppController {
// res.json(messages);
}
@UseGuards(JwtAuthGuard)
@Get('/conv')
async getConv(@Request() req) {
@ -589,7 +588,7 @@ export class AppController {
async muteUser(@Body() data: any) {
if (!data.username)
return ;
return await this.chatService.muteUser(data.convId, data.username, data.time)
return await this.chatService.muteUser(data.convId, data.username)
}
@UseGuards(JwtAuthGuard)
@ -599,16 +598,11 @@ 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, true)
}
@UseGuards(JwtAuthGuard)
@Post('/public')
async setPublic(@Body() data: any) {
return await this.chatService.setPrivate(data.convId, false)
return await this.chatService.setPrivate(data.convId)
}
@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/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
}
async muteUser(convId: number, username: string, time: string) {
async muteUser(convId: number, username: 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) {
@ -157,14 +149,12 @@ async isAdmin(convId: number, username: string) {
return (0);
}
async setPrivate(convId: number, bool: boolean) {
async setPrivate(convId: number) {
const conv = await this.findConv(convId);
console.log("bool= ", bool);
conv.private = bool;
// if (conv.private === true)
// conv.private = false;
// else
// conv.private = true;
if (conv.private === true)
conv.private = false;
else
conv.private = true;
this.save(conv);
}

View File

@ -3,10 +3,10 @@
/* ::: :::::::: */
/* config.service.ts :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sadjigui <sadjigui@student.42.fr> +#+ +:+ +#+ */
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* 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 {
@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/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});
}
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);
}
@ -75,8 +71,6 @@ 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);
@ -102,24 +96,16 @@ export class UsersService {
async getHistory(username: string) {
const user = await this.findOne(username);
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);
if (user) {
const children = user.children;
console.log(user);
console.log(user.children); // or perform any operations with the children
return children;
}
// 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) {
const user2 = await this.findOne(username)
if (!user)
if (!(await this.findOne(username)))
return (0);
// user.friendRequest = user.friendRequest || [];
user.friends = user.friends || [];
@ -131,9 +117,6 @@ 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,12 +6,10 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* 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 { Server, Socket } from 'socket.io';
import { v4 as uuidv4 } from 'uuid';
@ -167,9 +165,8 @@ 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', payload);
player.emit('pong:gameId', gameId);
});
}
@ -203,8 +200,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);
this.clients[playersIds[1]].emit('pong:gameId', payload);
this.clients[playersIds[0]].emit('pong:gameId', payload.gameId);
this.clients[playersIds[1]].emit('pong:gameId', payload.gameId);
}
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')
getName(client: Socket, payload: any): void
{
@ -368,11 +347,11 @@ addPrivateParty(client: Socket, payload: any): void {
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)
{
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_SOCKET_URL=bess-f2r2s16
REACT_APP_BASE_URL=localhost:8080
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=8081 react-scripts start",
"start": "HOST=0.0.0.0 PORT=8001 react-scripts start",
"start:dev": "npm run start --watch",
"build": "react-scripts build",
"test": "react-scripts test",

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 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>
<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>
</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/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();
}, [])
})
// console.log(index);
return (

View File

@ -21,15 +21,18 @@ 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,7 +27,6 @@ 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;
@ -120,7 +119,7 @@ function Chats(){
setUsers(tmpUsers.data);
// 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`);
socket.current.emit('connection', {username: tmpUser.data.username})
socket.current.on('message', (data) => { //data should be a message ?)
@ -206,12 +205,11 @@ function Chats(){
getMessage();
}, [currentChat]);
const handleSubmit = async (e: { key?: any; preventDefault: any; })=>{
const handleSubmit = async (e: { preventDefault: () => void; })=>{
e.preventDefault();
// console.log(`e= ${e.key}`)
// console.log(`name= ${user.username}`)
// let message;
console.log("in handle");
if (!user || !currentChat)
return ;
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}`)
if (e.key !== "Enter")
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 () => {
try{
console.log("friend= ", friend);
const res = await api.post("/invite", {username: friend})
// if (res.data === 1)
// console.log("res in friend= ", res)
@ -352,7 +371,6 @@ 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
@ -371,13 +389,12 @@ function Chats(){
<div className="chat">
<div className='navbar'>
{/* <img src={DefaultPic} alt="profile" className="pic"/> */}
<IoLogoOctocat className="catchat"/>
<img src={DefaultPic} alt="profile" className="pic"/>
<span>
{isLoading || !user ? (
<h4>Loading...</h4>
) : (
<h2>Chat</h2>
<h4>{user.nickname}</h4>
)}
</span>
{/* <div className="end">
@ -446,7 +463,7 @@ function Chats(){
))}
<TouchDiv>
<motion.div onClick={handleAddFriend}>
<MdOutlineGroupAdd className="catchat"/>
<MdOutlineGroupAdd />
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{showAddFriendAlert && addFriend && (
@ -459,7 +476,7 @@ function Chats(){
</TouchDiv>
<TouchDiv>
<motion.div onClick={handleBlockFriend}>
<ImBlocked className="block"/>
<ImBlocked />
</motion.div>
<AnimatePresence initial={false} onExitComplete={() => null}>
{showBlockAlert && block && (

View File

@ -6,7 +6,7 @@
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* 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 scrollRef = useRef<HTMLDivElement>(null);
// console.log("Message eher")
useEffect(() => {
if (scrollRef.current)
{
scrollRef.current.scrollIntoView({ behavior: "smooth"});
}})
useEffect(() => {
scrollRef.current.scrollIntoView({ behavior: "smooth",})
}
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})
@ -82,37 +81,23 @@ 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)
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 (<></>);
}
// 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))/
console.log("no return message good");
return (
<div className={own ? "meMessage" : "youMessage"} ref={scrollRef}>
<div>

View File

@ -7,7 +7,6 @@ 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",
@ -22,19 +21,16 @@ const dropIn = {
}},
exit:{y: "100vh",
opacity: 0,},
};
interface ModalProps {
handleClose: Function,
}
const Modal = ({handleClose}: ModalProps) => {
const Modal = ({handleClose}) => {
// const [multi, setMulti] = useState(false);
const [selectTags, setSelectTag] = useState([{ id: 1, selectedOption: ''}]);
const [selectedOptionArray, setSelectedOptionArray] = useState<string[]>([]);
const [users, setUsers] = useState<User[]>([]);
const [user, setUser] = useState<User>();
const [convs, setConvs] = useState<Conv[]>([]);
const [selectedOptionArray, setSelectedOptionArray] = useState([]);
const [users, setUsers] = useState([]);
const [user, setUser] = useState();
const [convs, setConvs] = useState([]);
const [channel, setChannel] = useState('');
@ -57,7 +53,7 @@ const Modal = ({handleClose}: ModalProps) => {
getConv();
}, []);
const handleOptionChange = (selectId: number, selectedOption: string) => {
const handleOptionChange = (selectId, selectedOption) => {
console.log("selected Option=", selectedOption)
setSelectTag((prevTags) =>
prevTags.map((tag) =>
@ -107,15 +103,12 @@ const Modal = ({handleClose}: ModalProps) => {
<Backdrop onClick={handleClose}>
<motion.div
onClick={(e) => e.stopPropagation()}
className="modalSetting"
className="modal"
// variant={dropIn}
initial="hidden"
animate="visible"
exit="exit"
>
{/* <p>New Conversation</p> */}
<div className="settingFirstPart2">
{selectTags.map((selectTag) => (
<div key={selectTag.id}>
<select
@ -125,9 +118,9 @@ const Modal = ({handleClose}: ModalProps) => {
<option value="">{
selectTag.selectedOption ? selectTag.selectedOption : "Select an 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}>
{item.nickname}
{item.username}
</option>
))}
</select>
@ -139,12 +132,10 @@ const Modal = ({handleClose}: ModalProps) => {
<div className="div_submit">
<Link to='#' className="submit" onClick={ saveSelectedOptions}>Submit</Link>
<Link to="#" className="submit" onClick={() => handleClose}>Cancel</Link>
</div>
<Link to="#" className="submit" onClick={handleClose}>Cancel</Link>
</div>
<div className="settingSecondPart">
{convs.length > 0 && (
<select
@ -154,7 +145,7 @@ const Modal = ({handleClose}: ModalProps) => {
>
<option value="">Select an option</option>
{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}>
{conv.name}
</option>
@ -162,9 +153,9 @@ const Modal = ({handleClose}: ModalProps) => {
))}
</select>
)}
{/* {channel.private ? (
<input className="mdp" placeholder="passdddddword" type="text" />
):("")} */}
{channel.private ? (
<input className="mdp" placeholder="password" type="text" />
):("")}
<div className="div_submit">
@ -173,7 +164,6 @@ const Modal = ({handleClose}: ModalProps) => {
</div>
</motion.div>
</Backdrop>
)

View File

@ -39,10 +39,8 @@ 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<Boolean>();
const [loading, setLoading] = useState<Boolean>(true);
const [privateConv, setPrivateConv] = useState(false);
const dark = () => setPrivateConv(true);
const light = () => setPrivateConv(false);
const [mute, setMute] = useState(false);
@ -55,15 +53,9 @@ 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)
}
@ -71,31 +63,6 @@ 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([]);
@ -113,30 +80,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")
@ -190,15 +157,11 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
handleClose();
};
const handleMute = async (e: { key: string; }) => {
console.log(`e in press= ${e.key}`)
if (e.key != "Enter")
const handleMute = async () => {
if (!selectedUser.length)
return ;
// console.log("value mute = ", e.target.value);
console.log("value mute = ", time);
try{
await api.post("/mute", {convId: convId, username: selectedUser, time: time})
await api.post("/mute", {convId: convId, username: selectedUser})
} catch(err) {
console.log(err);
}
@ -214,17 +177,6 @@ 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
@ -246,7 +198,7 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
<p className="checkbox">Password<input type="checkbox" value="password" checked={password} onChange={handleCheckPass}/> </p>
{password ? (
{password || privateConv ? (
<input
onChange={(e) => setNewPassword(e.target.value)}
onKeyDown={handlePassword}
@ -256,6 +208,7 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
):
("")}
</div>
<div className="forName">
<input
@ -301,14 +254,7 @@ const ModalSetting = ({handleClose, convId, socket }: ModalSettingProps) => {
</div>
{mute ? (
<input
onKeyDown={handleMute}
type="number"
className="in_howLong"
placeholder="How long ?"
value={time}
onChange={(e) => setTime(e.target.value)}
/>
<input type="text" className="in_howLong" placeholder="How long ?" />
):("")}
</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 {Link} from 'react-router-dom';
// import { UserProfile } from "../../DataBase/DataUserProfile";
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: {
@ -30,48 +29,30 @@ const dropIn = {
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);
// }
try{
await api.post("/nickname", {nickname: nickname})
// setUser(tmpUser.data);
// setIsLoading(false)
}
catch(err){
console.log(err);
}
};
postNickname();
}
const handlePostNickname = async () => {
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);
await api.post("/nickname", {nickname: nickname})
window.location.reload();
}
else {
console.log("nickname already set = ", ret.data);
setErrTaken(true);
}
// setUser(tmpUser.data);
// setIsLoading(false)
}
@ -92,27 +73,16 @@ const ModalEdit = (handleClose) => {
animate="visible"
exit="exit">
<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 className="button" onClick={handlePostNickname}>
<div className="button" onClick={ () => handlePostNickname()}>
change
{/* <Link className="button" to={""}>change</Link> */}
</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 {user.win}/{user.loss}</h2>
<h2 className='title'>Match history Win/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/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);
}
};
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,14 +101,13 @@ export default function Friend({currentUser}: UserProps)
<img className="pic-user" src={DefaultPicture} alt="Default Profile Picture" />
)}
{request ? (
<div className="end">
<div className="infoSideBar">
<span onClick={() => handleButtonClick(currentUser)}>{currentUser.nickname}</span>
<div className="end">
<RxCheckCircled className="friendRequest" onClick={() => Accept(request)} color={'green'}/>
<RxCircleBackslash className="friendRequest" onClick={() => Refuse(request)} color={'red'}/>
</div>
<RxCheckCircled onClick={() => Accept(request)} color={'green'}/>
<RxCircleBackslash onClick={() => Refuse(request)} color={'red'}/>
</div>
) : ( "" )}
</UserChat>
)
}

View File

@ -7,7 +7,6 @@ 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"
@ -89,15 +88,26 @@ function Social (){
<div>
<div className='navbar'>
{/* <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>
{isLoading || !user ? (
<h4>Loading...</h4>
) : (
<h2>Social</h2>
<h4>{user.nickname}</h4>
)}
</span>
<div className="end">
<TouchDiv>
<MdOutlineGroupAdd/>
</TouchDiv>
<TouchDiv>
<ImBlocked/>
</TouchDiv>
</div>
</div>
{/* map with fiend request */}
@ -109,7 +119,6 @@ function Social (){
{friends.map(c=> (
<Friend currentUser={c}/>
))}
</div>
)
}

View File

@ -3,10 +3,10 @@
/* ::: :::::::: */
/* Home.tsx :+: :+: :+: */
/* +:+ +:+ +:+ */
/* By: sadjigui <sadjigui@student.42.fr> +#+ +:+ +#+ */
/* By: apommier <apommier@student.42.fr> +#+ +:+ +#+ */
/* +#+#+#+#+#+ +#+ */
/* 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 { Link } from "react-router-dom";
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 { 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(()=> {
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)
{
@ -130,7 +160,7 @@ function Profile () {
{isLoading || !user ? (
<h1>Loading...</h1>
) : (
<h1 className='user_name'>{user.nickname}</h1>
<h1>{user.nickname}</h1>
)}
</span>
@ -172,7 +202,7 @@ function Profile () {
function Home () {
const [move, setmove ] = useState(false);
const [user, setUser] = useState<User>();
const [user, setUser] = useState([]);
const [successQr, setSuccessQr] = useState(false);
const [successSword, setSuccessSword] = useState(false);
@ -181,31 +211,20 @@ function Home () {
const closeSword = () => setSuccessSword(false);
const closeCrown = () => setSuccessCrown(false);
const { username } = useParams();
useEffect(() => {
const fetchSuccess = async () => {
try {
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)
{
console.log(error);
}
};
fetchSuccess();
}, []);
})
return (
<motion.div className="page"
@ -213,13 +232,14 @@ function Home () {
animate={{opacity: 1}}
exit={{opacity: -1}}>
<div>
{user && user.otp_verified ? (
{user.otp_verified ? (
<MdQrCodeScanner className='success' onClick={() => setSuccessQr(true)}/>
):("")}
{user && user.win >= 2 ? (
{user.win >= 2 ? (
<GiWingedSword className="success" onClick={() => setSuccessSword(true)}/>
):("")}
{user && user.win >= 5 ? (
{user.win >= 5 ? (
<GiCrownedSkull className="success" onClick={() => setSuccessCrown(true)}/>
):("")}
</div>
@ -236,7 +256,7 @@ function Home () {
className="div_history"
// className="history"
onClick={ () => setmove(!move)}>
<Link to="#" className="history"> {move ? (<AiOutlineCloseCircle/>):(<AiOutlineHistory/>)} Match History</Link>
<Link to="#" className="history"><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="number"
type="text"
className="qr"
placeholder="6 Digits 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)
return ;
const socket = io('http://' + process.env.REACT_APP_SOCKET_URL + ':4000', { transports: ['polling'] });
const socket = io('http://localhost:4000', { transports: ['polling'] });
// useEffect(() => {
// console.log("useeffect?????????????????")
// return () => {
@ -170,9 +170,7 @@ socket.on('pong:privateId', async (data) => {
socket.on('pong:gameId', async (data) => {
console.log("gameId received");
gameId = data.gameId;
console.log("gameid = ", gameId);
console.log("data gameid = ", data);
gameId = data;
try {
let response = await api.get('/profile');
@ -192,16 +190,6 @@ 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
@ -210,11 +198,7 @@ socket.on('pong:gameId', async (data) => {
});
socket.on('pong:name', (data) => {
opName = data.name;
// if (data.myId === myId)
// vX = 0.0001;
// else
// vX = -0.0001;
opName = data;
console.log(`opponent Name= ${opName}`)
});
@ -238,6 +222,7 @@ 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
@ -266,27 +251,12 @@ socket.on('pong:point', (data) => {
// console.log("up point");
myScore = data.point;
// }
vX = -0.0001;
vX = 0;
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
@ -353,26 +323,6 @@ socket.on('pong:hisPoint', (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()
@ -509,10 +459,7 @@ 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);
@ -646,17 +593,16 @@ async function draw(timestamp: number)
}
ballX = canvas.width / 2;
ballY = canvas.height / 2;
vX = 0.0001;
vX = 0;
vY = 0;
hisScore += 1;
send_point();
// 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)
// socket.emit
// console.log("win point")
// if (ballY <= paddleY + paddleHeight + ballRadius && ballY >= paddleY - ballRadius)
// {
@ -756,8 +702,9 @@ async function draw(timestamp: number)
vX -= 0.0001;
}
send_forced_info();
// console.log(`vx = ${vX}`);
}
else if (event.code === "KeyW")
else if (event.code === "KeyR")
{
if (!superpowerModifier)
return ;
@ -770,13 +717,6 @@ 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,15 +3,12 @@ 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<User>();
const [user, setUser] = useState(false);
useEffect(() => {
if (!data) {
@ -40,7 +37,7 @@ function SuccessToken() {
getUser();
}, [data]);
const handleKeyPress = async (e: { key: string; })=>{
const handleKeyPress = async (e)=>{
// console.log(`e in press= ${e.key}`)
if (e.key !== "Enter")
return ;
@ -93,8 +90,7 @@ 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,30 +5,15 @@
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: auto;
width: 20%;
border-radius: 5px;
background-color: rgb(0, 0, 0);
margin : 1%;
color:rgb(42, 41, 41);
border-style: solid;
border-width: 1px;
color:white;
}
.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;
@ -63,9 +48,3 @@ input.qr{
transform: rotate(360deg);
}
}
.friendRequest {
margin-left: 4vh;
stroke-width: 0.5;
font-size: x-large;
}

View File

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

View File

@ -14,9 +14,19 @@ 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