From fb41310ea1aae0af7552aa9e130a91590f19fc00 Mon Sep 17 00:00:00 2001 From: Wadie-ess Date: Fri, 10 Nov 2023 23:27:43 +0100 Subject: [PATCH] working on finishing chat and other things --- backend/code/src/auth/auth.controller.ts | 11 + backend/code/src/auth/auth.service.ts | 14 + .../code/src/auth/stratgies/ft.strategy.ts | 2 +- backend/code/src/game/game.service.ts | 11 +- backend/code/src/gateways/gateways.gateway.ts | 55 +-- backend/code/src/messages/messages.service.ts | 2 +- backend/code/src/rooms/rooms.controller.ts | 13 + backend/code/src/rooms/rooms.service.ts | 47 +++ backend/code/src/users/dto/two-factor.dto.ts | 25 +- backend/code/src/users/users.controller.ts | 9 +- backend/code/src/users/users.service.ts | 86 ++--- frontend/code/package-lock.json | 43 ++- frontend/code/package.json | 3 +- .../Chat/Components/Conversation.tsx | 168 ++++++---- .../Components/Chat/Components/RecentChat.tsx | 18 +- .../Chat/Components/RoomChatHelpers.tsx | 40 ++- .../Chat/Components/UserToUserChat.tsx | 37 +- .../Chat/Controllers/RoomChatControllers.tsx | 2 +- .../Components/Chat/Services/ChatServices.ts | 19 ++ frontend/code/src/Components/Chat/index.tsx | 7 +- .../Components/FirstLogin/UploadAvatar.tsx | 149 ++++++--- .../src/Components/FirstLogin/UploadLogic.tsx | 98 +++--- .../code/src/Components/FirstLogin/index.tsx | 28 +- .../code/src/Components/Home/assets/Table.tsx | 220 ++++++------ frontend/code/src/Components/Home/index.tsx | 54 ++- .../src/Components/Layout/Assets/Avatar.tsx | 65 ++-- .../src/Components/Layout/Assets/Logo.tsx | 86 +++-- frontend/code/src/Components/Layout/index.tsx | 52 +-- .../code/src/Components/Loading/index.tsx | 24 +- frontend/code/src/Components/Login/index.tsx | 2 +- .../src/Components/Profile/assets/Table.tsx | 6 +- .../code/src/Components/Profile/index.tsx | 7 +- .../src/Components/Settings/assets/Avatar.tsx | 18 +- .../src/Components/Settings/assets/Inputs.tsx | 315 ++++++++++-------- .../code/src/Components/Settings/index.tsx | 246 +++++++++++--- .../code/src/Components/Validate2Fa/index.tsx | 85 +++++ .../code/src/Components/images/app-store.svg | 5 + .../images/google-authenticator.svg | 26 ++ .../code/src/Components/images/play-store.svg | 38 +++ frontend/code/src/Routes/index.tsx | 7 + frontend/code/src/Stores/stores.ts | 168 +++++----- frontend/code/src/Utils/helpers.ts | 5 +- frontend/code/src/logo.svg | 1 - frontend/code/tsconfig.json | 1 - 44 files changed, 1527 insertions(+), 791 deletions(-) create mode 100644 frontend/code/src/Components/Validate2Fa/index.tsx create mode 100644 frontend/code/src/Components/images/app-store.svg create mode 100644 frontend/code/src/Components/images/google-authenticator.svg create mode 100644 frontend/code/src/Components/images/play-store.svg delete mode 100644 frontend/code/src/logo.svg diff --git a/backend/code/src/auth/auth.controller.ts b/backend/code/src/auth/auth.controller.ts index a3c60d4..5252f9b 100644 --- a/backend/code/src/auth/auth.controller.ts +++ b/backend/code/src/auth/auth.controller.ts @@ -8,6 +8,7 @@ import { Res, HttpCode, HttpStatus, + Param } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthDto } from './dto/auth.dto'; @@ -107,4 +108,14 @@ export class AuthController { path: '/auth', }); } + + @Get('validatToken/:token') + async validatToken( + @Param('token') token: string, + @Res({ passthrough: true }) res: Response, + ) { + return this.authService.checkToken(token); + } } + + diff --git a/backend/code/src/auth/auth.service.ts b/backend/code/src/auth/auth.service.ts index c32668e..12de0ef 100644 --- a/backend/code/src/auth/auth.service.ts +++ b/backend/code/src/auth/auth.service.ts @@ -106,4 +106,18 @@ export class AuthService { } return { isValid }; } + + + async checkToken(tfaToken: string) + { + const user = await this.prisma.user.findUnique({ + where: { tfaToken }, + select: { + tfaToken: true + }, + }); + if(!user) + return false; + return true; + } } diff --git a/backend/code/src/auth/stratgies/ft.strategy.ts b/backend/code/src/auth/stratgies/ft.strategy.ts index 764d490..2aeb8ca 100644 --- a/backend/code/src/auth/stratgies/ft.strategy.ts +++ b/backend/code/src/auth/stratgies/ft.strategy.ts @@ -36,7 +36,7 @@ export class FtStrategy extends PassportStrategy(Strategy, '42') { if (user.tfaEnabled) { const tfaToken = crypto.randomBytes(20).toString('hex'); await this.usersService.updateUser(user.userId, { tfaToken }); - res.redirect(process.env.FRONT_URL + '/tfa/' + tfaToken); + res.redirect(process.env.FRONT_URL + '/2fa/validate/' + tfaToken); return cb(null, profile); } diff --git a/backend/code/src/game/game.service.ts b/backend/code/src/game/game.service.ts index 5f92dc4..1cbac51 100644 --- a/backend/code/src/game/game.service.ts +++ b/backend/code/src/game/game.service.ts @@ -10,13 +10,22 @@ export class GameService { private readonly prisma: PrismaService, private eventEmitter: EventEmitter2, ) { - this.launchGame(); + //this.launchGame(); } private waitingPlayers: Socket[] = []; @OnEvent('game.start') handleGameStartEvent(client: Socket) { + const index = this.waitingPlayers.find((player) => { + return player.data.user.sub === client.data.user.sub; + } + ); + if (index) { + console.log('client already in the queue'); + return; + } + this.waitingPlayers.push(client); console.log('client subscribed to the queue'); } diff --git a/backend/code/src/gateways/gateways.gateway.ts b/backend/code/src/gateways/gateways.gateway.ts index 3f2cdb4..7271a21 100644 --- a/backend/code/src/gateways/gateways.gateway.ts +++ b/backend/code/src/gateways/gateways.gateway.ts @@ -13,6 +13,7 @@ import { EventEmitter2, OnEvent } from '@nestjs/event-emitter'; import { PrismaService } from 'src/prisma/prisma.service'; import { Game } from 'src/game/game'; import { $Enums, Notification } from '@prisma/client'; +import { Client } from 'socket.io/dist/client'; @WebSocketGateway(3004, { cors: { @@ -87,7 +88,6 @@ export class Gateways implements OnGatewayConnection, OnGatewayDisconnect { @OnEvent('sendMessages') sendMessage(message: MessageFormatDto) { - console.log('recive msg !'); const chanellname: string = `Room:${message.roomId}`; this.server.to(chanellname).emit('message', message); } @@ -154,14 +154,20 @@ export class Gateways implements OnGatewayConnection, OnGatewayDisconnect { }, }, }); - const roomMembers = await this.prisma.roomMember.findMany({ + let roomMembers = await this.prisma.roomMember.findMany({ where: { roomId: message.roomId, }, select: { userId: true, + is_banned: true, }, }); + + roomMembers = roomMembers.filter( + (member) => member.userId !== message.authorId && member.userId !== notif.actorId && !member.is_banned, + ); + const clientsSockets = await this.server .in(`Room:${message.roomId}`) .fetchSockets(); @@ -221,21 +227,29 @@ export class Gateways implements OnGatewayConnection, OnGatewayDisconnect { } @SubscribeMessage('joinRoom') - async handleJoinRoomEvent(data: any) { + async handleJoinRoomEvent(client: Socket, data: any) { + const userId = client.data.user.sub; const member = await this.prisma.roomMember.findFirst({ where: { userId: data.memberId, roomId: data.roomId, }, }); - if (member && !member.is_banned) { - const banedClientSocket = await this.server - .in(`User:${data.memberId}`) - .fetchSockets(); - if (banedClientSocket.length > 0) { - banedClientSocket[0].join(`Room:${data.roomId}`); - } + if (member && !member.is_banned && userId === data.memberId) { + client.join(`Room:${data.roomId}`); } + + } + + @SubscribeMessage('PingOnline') + async handlePingOnlineEvent(client: Socket, data: any) { + const userId = client.data.user.sub; + const friendId = data.friendId; + if (this.server.sockets.adapter.rooms.get(`User:${friendId}`)?.size) { + client.emit('friendOnline', friendId); + return true; + } + return false; } @SubscribeMessage('unban') @@ -269,18 +283,19 @@ export class Gateways implements OnGatewayConnection, OnGatewayDisconnect { async hundleDeparture( @MessageBody() data: { roomId: string; memberId: string; type: string }, ) { - const clients = await this.server.in(`Room:${data.roomId}`).fetchSockets(); - console.log(`Room:${data.roomId}`); - const clientToBan = clients.find( + const clients = await this.server.in(`User:${data.memberId}`).fetchSockets(); + const clientsToBan = clients.filter( (client) => client.data.user.sub === data.memberId, ); - if (clientToBan) { - clientToBan.leave(`Room:${data.roomId}`); - if (data?.type === 'kick') { - clientToBan.emit('roomDeparture', { - roomId: data.roomId, - type: 'kick', - }); + if (clientsToBan.length) { + for await (const client of clientsToBan) { + client.leave(`Room:${data.roomId}`); + if (data?.type === 'kick') { + client.emit('roomDeparture', { + roomId: data.roomId, + type: 'kick', + }); + } } } } diff --git a/backend/code/src/messages/messages.service.ts b/backend/code/src/messages/messages.service.ts index 0f7bc62..3ea3528 100644 --- a/backend/code/src/messages/messages.service.ts +++ b/backend/code/src/messages/messages.service.ts @@ -102,7 +102,7 @@ export class MessagesService { const responseMessage: MessageFormatDto = new MessageFormatDto(messageData); this.eventEmitter.emit('sendNotification', { actorId: userId, - type: $Enums.NotifType.addFriend, + type: $Enums.NotifType.message, entityId: messageData.id, entity_type: 'message', }); diff --git a/backend/code/src/rooms/rooms.controller.ts b/backend/code/src/rooms/rooms.controller.ts index ca34279..658c898 100644 --- a/backend/code/src/rooms/rooms.controller.ts +++ b/backend/code/src/rooms/rooms.controller.ts @@ -219,4 +219,17 @@ export class RoomsController { ) { return this.roomsService.getDMs(userId, offset, limit); } + + @Get('dm/:id') + @ApiResponse({ + status: HttpStatus.OK, + }) + @HttpCode(HttpStatus.OK) + @UseGuards(AtGuard) + async getDM( + @GetCurrentUser('userId') userId: string, + @Param('id') dmId: string, + ) { + return this.roomsService.getDM(userId, dmId); + } } diff --git a/backend/code/src/rooms/rooms.service.ts b/backend/code/src/rooms/rooms.service.ts index 85b94d1..17a263c 100644 --- a/backend/code/src/rooms/rooms.service.ts +++ b/backend/code/src/rooms/rooms.service.ts @@ -645,6 +645,7 @@ export class RoomsService { firstName: true, lastName: true, avatar: true, + discreption: true, }, }, }, @@ -680,9 +681,55 @@ export class RoomsService { secondMemberId: secondMember.user.userId, last_message, avatar, + bio: secondMember.user.discreption, }; }), ); return dmsData; } + + async getDM(userId: string, dmId: string) { + const room = await this.prisma.room.findUnique({ + where: { + id: dmId, + }, + select: { + id: true, + type: true, + ownerId: true, + members: { + select: { + user: { + select: { + userId: true, + firstName: true, + lastName: true, + avatar: true, + discreption: true, + }, + }, + }, + }, + }, + }); + if (!room) throw new HttpException('room not found', HttpStatus.NOT_FOUND); + const member = room.members.find((member) => member.user.userId === userId); + if (!member) + throw new UnauthorizedException('you are not a member of this room'); + const secondMember = room.members.find( + (member) => member.user.userId !== userId, + ); + const avatar: PICTURE = { + thumbnail: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_48,w_48/${secondMember.user.avatar}`, + medium: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_72,w_72/${secondMember.user.avatar}`, + large: `https://res.cloudinary.com/trandandan/image/upload/c_thumb,h_128,w_128/${secondMember.user.avatar}`, + }; + return { + id: room.id, + name: secondMember.user.firstName + ' ' + secondMember.user.lastName, + secondMemberId: secondMember.user.userId, + avatar, + bio: secondMember.user.discreption, + }; + } } diff --git a/backend/code/src/users/dto/two-factor.dto.ts b/backend/code/src/users/dto/two-factor.dto.ts index d272332..6440b97 100644 --- a/backend/code/src/users/dto/two-factor.dto.ts +++ b/backend/code/src/users/dto/two-factor.dto.ts @@ -1,6 +1,25 @@ -import { IsBoolean } from 'class-validator'; +import { IsBoolean, IsString, IsNotEmpty, IsEnum, IsOptional} from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +export enum TwoFactorAction { + ENABLE = 'enable', + DISABLE = 'disable', +} export class TwoFactorDto { - @IsBoolean() - activate: boolean; + @IsString() + @IsNotEmpty() + @IsOptional() + @ApiProperty({example: "MC41emVyMjFqbG4w"}) + secret: string; + + @IsString() + @IsNotEmpty() + @ApiProperty({example: "745896"}) + otp: string; + + @IsString() + @IsNotEmpty() + @IsEnum(TwoFactorAction) + @ApiProperty({example: TwoFactorAction.ENABLE}) + action: string; } diff --git a/backend/code/src/users/users.controller.ts b/backend/code/src/users/users.controller.ts index 8e9ac7e..fbe8db1 100644 --- a/backend/code/src/users/users.controller.ts +++ b/backend/code/src/users/users.controller.ts @@ -27,19 +27,14 @@ export class UsersController { return this.usersService.getUsers(query.q); } - @Post('twoFactorAuth') + @Post('enableTwoFactorAuth') @HttpCode(HttpStatus.OK) @UseGuards(AtGuard) async twoFactorAuth( @Body() dataDto: TwoFactorDto, @GetCurrentUser('userId') userId: string, ) { - return this.usersService.twoFactorAuth(userId, dataDto.activate); + return this.usersService.twoFactorAuth(userId, dataDto); } - @Get('2faQrCode') - @UseGuards(AtGuard) - async get2faQrCode(@GetCurrentUser('userId') userId: string) { - return this.usersService.genertQrcode(userId); - } } diff --git a/backend/code/src/users/users.service.ts b/backend/code/src/users/users.service.ts index 6f8a3c3..703ce7a 100644 --- a/backend/code/src/users/users.service.ts +++ b/backend/code/src/users/users.service.ts @@ -4,7 +4,7 @@ import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; import { NAME, PICTURE } from '../profile/dto/profile.dto'; import { authenticator } from 'otplib'; -import { toDataURL } from 'qrcode'; +import { TwoFactorDto, TwoFactorAction } from './dto/two-factor.dto'; @Injectable() export class UsersService { @@ -142,51 +142,57 @@ export class UsersService { }); } - async twoFactorAuth(userId: string, tfaEnabled: boolean) { - const user = await this.getUserById(userId); - if (!user) { - throw new Error('User not found'); - } - - if (user.tfaEnabled === tfaEnabled) { - throw new HttpException( - `Two factor authentication is already ${ - tfaEnabled ? 'activated' : 'deactivated' - }`, - 400, - ); - } - - const secret = authenticator.generateSecret(); - await this.prisma.user.update({ - where: { userId }, - data: { - tfaEnabled, - ...(tfaEnabled ? { tfaSecret: secret } : { tfaSecret: null }), - }, - }); - - return { - message: `Two factor authentication has been ${ - tfaEnabled ? 'activated' : 'deactivated' - }`, - }; - } - - async genertQrcode(userId: string) { + async twoFactorAuth(userId: string, dataDto: TwoFactorDto) { const user = await this.prisma.user.findUnique({ where: { userId }, }); if (!user) { - return null; + throw new HttpException('User not found', 404); + } + + if (dataDto.action === TwoFactorAction.ENABLE && !user.tfaEnabled) { + console.log(dataDto); + // generate a top with the same secret + console.log(authenticator.generate(dataDto.secret)); + const isValid = authenticator.verify({ + token: dataDto.otp, + secret: dataDto.secret, + }); + console.log(authenticator.keyuri('test', 'test', dataDto.secret)); + + if (!isValid) { + throw new HttpException('Invalid OTP', 400); + } + await this.prisma.user.update({ + where: { userId }, + data: { + tfaEnabled: true, + tfaSecret: dataDto.secret, + }, + }); + return { message: 'Two factor authentication enabled' }; } - const otpauth = authenticator.keyuri( - user.Username, - 'PongGame', - user.tfaSecret, - ); - return toDataURL(otpauth); + if (dataDto.action === TwoFactorAction.DISABLE && user.tfaEnabled) { + const isValid = authenticator.verify({ + token: dataDto.otp, + secret: user.tfaSecret, + }); + + if (!isValid) { + throw new HttpException('Invalid OTP', 400); + } + + await this.prisma.user.update({ + where: { userId }, + data: { + tfaEnabled: false, + tfaSecret: null, + }, + }); + + return { message: 'Two factor authentication disabled' }; + } } } diff --git a/frontend/code/package-lock.json b/frontend/code/package-lock.json index 3534222..0852662 100644 --- a/frontend/code/package-lock.json +++ b/frontend/code/package-lock.json @@ -32,8 +32,10 @@ "react-icons": "^4.10.1", "react-infinite-scroll-component": "^6.1.0", "react-konva": "^18.2.10", + "react-qr-code": "^2.0.12", "react-scripts": "^5.0.1", "socket.io-client": "^4.7.2", + "tailwind-merge": "^2.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4", "zustand": "^4.4.1" @@ -2094,9 +2096,9 @@ "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.15.tgz", - "integrity": "sha512-T0O+aa+4w0u06iNmapipJXMV4HoUir03hpx3/YqXXhu9xim3w+dVphjFWl1OH8NbZHw5Lbm9k45drDkgq2VNNA==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.23.2.tgz", + "integrity": "sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -14345,6 +14347,11 @@ "teleport": ">=0.2.0" } }, + "node_modules/qr.js": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/qr.js/-/qr.js-0.0.0.tgz", + "integrity": "sha512-c4iYnWb+k2E+vYpRimHqSu575b1/wKl4XFeJGpFmrJQz5I88v9aY2czh7s0w36srfCM1sXgC/xpoJz5dJfq+OQ==" + }, "node_modules/qs": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", @@ -14665,6 +14672,24 @@ "react-dom": "^0.14 || ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/react-qr-code": { + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/react-qr-code/-/react-qr-code-2.0.12.tgz", + "integrity": "sha512-k+pzP5CKLEGBRwZsDPp98/CAJeXlsYRHM2iZn1Sd5Th/HnKhIZCSg27PXO58zk8z02RaEryg+60xa4vyywMJwg==", + "dependencies": { + "prop-types": "^15.8.1", + "qr.js": "0.0.0" + }, + "peerDependencies": { + "react": "^16.x || ^17.x || ^18.x", + "react-native-svg": "*" + }, + "peerDependenciesMeta": { + "react-native-svg": { + "optional": true + } + } + }, "node_modules/react-reconciler": { "version": "0.29.0", "resolved": "https://registry.npmjs.org/react-reconciler/-/react-reconciler-0.29.0.tgz", @@ -16380,6 +16405,18 @@ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, + "node_modules/tailwind-merge": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.0.0.tgz", + "integrity": "sha512-WO8qghn9yhsldLSg80au+3/gY9E4hFxIvQ3qOmlpXnqpDKoMruKfi/56BbbMg6fHTQJ9QD3cc79PoWqlaQE4rw==", + "dependencies": { + "@babel/runtime": "^7.23.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.3.tgz", diff --git a/frontend/code/package.json b/frontend/code/package.json index 395a475..c89735d 100644 --- a/frontend/code/package.json +++ b/frontend/code/package.json @@ -27,9 +27,10 @@ "react-icons": "^4.10.1", "react-infinite-scroll-component": "^6.1.0", "react-konva": "^18.2.10", - "socket.io-client": "^4.7.2", + "react-qr-code": "^2.0.12", "react-scripts": "^5.0.1", "socket.io-client": "^4.7.2", + "tailwind-merge": "^2.0.0", "typescript": "^4.9.5", "web-vitals": "^2.1.4", "zustand": "^4.4.1" diff --git a/frontend/code/src/Components/Chat/Components/Conversation.tsx b/frontend/code/src/Components/Chat/Components/Conversation.tsx index 52a642f..6bc2992 100644 --- a/frontend/code/src/Components/Chat/Components/Conversation.tsx +++ b/frontend/code/src/Components/Chat/Components/Conversation.tsx @@ -92,7 +92,7 @@ export const ConversationHeader: React.FC = ({ const LayoutState = useModalStore((state) => state); const ChatState = useChatStore((state) => state); const SelectedChat = useChatStore((state) => state.selectedChatID); - + const currentUser = useChatStore((state) => state.currentDmUser); const selectedChatType = useChatStore((state) => state.selectedChatType); @@ -102,7 +102,7 @@ export const ConversationHeader: React.FC = ({ const [isModalOpen, setIsModalOpen] = useState(false); const [isOnline, SetOnline] = useState(false); - const sockerStore = useSocketStore(); + const socketStore = useSocketStore(); const handleConfirmation = () => { setIsModalOpen(false); }; @@ -120,12 +120,12 @@ export const ConversationHeader: React.FC = ({ console.log("user offline", userId); }; - sockerStore.socket.on("friendOffline", handleOffline); - sockerStore.socket.on("friendOnline", handleOnline); + socketStore.socket.on("friendOffline", handleOffline); + socketStore.socket.on("friendOnline", handleOnline); return () => { - sockerStore.socket.off("friendOffline", handleOffline); - sockerStore.socket.off("friendOnline", handleOnline); + socketStore.socket.off("friendOffline", handleOffline); + socketStore.socket.off("friendOnline", handleOnline); }; // eslint-disable-next-line }, [ChatState.selectedChatID]); @@ -301,19 +301,58 @@ export const ConversationHeader: React.FC = ({ export const Conversation: React.FC = ({ onRemoveUserPreview, }) => { - const chatState = useChatStore((state) => state); + const chatState = useChatStore(); const messageContainerRef = useRef(null); const socketStore = useSocketStore(); const scrollToBottom = () => { if (messageContainerRef.current) { const container = messageContainerRef.current; - container.scrollTop = container.scrollHeight; + // container.scrollTop = container.scrollHeight; + container.scrollTo({ + top: container.scrollHeight, + behavior:'smooth' + }); } }; - + const [inputValue, setInputValue] = useState(""); const [FailToSendMessage, setFail] = useState(false); const [IsLoading, setLoading] = useState(true); + const currentUser = useUserStore((state) => state); + const handleMessage = (message: { + id: string; + avatar: { + thumbnail: string; + medium: string; + large: string; + }; + content: string; + time: string; + roomId: string; + authorId: string; + }) => { + console.log(message); + if (message.roomId === chatState.selectedChatID) { + const NewMessage: Message = { + avatar: message.avatar, + senderId: message.authorId, + message: message.content, + time: message.time, + }; + chatState.pushMessage(NewMessage); + scrollToBottom(); + } + }; + + const handleLeave = (event : { + roomId : string, + type : string, + })=> { + if(chatState.selectedChatID === event.roomId && event.type === "kick") + { + chatState.deleteRoom(event.roomId); + } + } const handleInputChange = (e: { target: { value: React.SetStateAction }; @@ -323,73 +362,66 @@ export const Conversation: React.FC = ({ }; useEffect(() => { scrollToBottom(); - }, [chatState.currentMessages?.length]); + }, [chatState.currentMessages?.length, IsLoading]); useEffect(() => { - const handleMessage = (message: { - id: string; - avatar: { - thumbnail: string; - medium: string; - large: string; - }; - content: string; - time: string; - roomId: string; - authorId: string; - }) => { - console.log(message); - if (message.roomId === chatState.selectedChatID) { - const NewMessage: Message = { - avatar: message.avatar, - senderId: message.authorId, - message: message.content, - time: message.time, - }; - chatState.pushMessage(NewMessage); - scrollToBottom(); - } - }; + setLoading(true); + + socketStore.socket.emit("joinRoom", { + memberId: currentUser.id, + roomId: chatState.selectedChatID, + }); + + // const handle + socketStore.socket.on("roomDeparture",handleLeave) socketStore.socket.on("message", handleMessage); const fetch = async () => { setLoading(true); - getRoomMessagesCall(chatState.selectedChatID, 0, 30).then((res) => { - setLoading(false); - if (res?.status !== 200 && res?.status !== 201) { - } else { - const messages: Message[] = []; - res.data.forEach( - (message: { - id: string; - avatar: { - thumbnail: string; - medium: string; - large: string; - }; - content: string; - time: string; - roomId: string; - authorId: string; - }) => { - messages.push({ - avatar: message.avatar, - senderId: message.authorId, - message: message.content, - time: message.time, - }); - } - ); - chatState.fillCurrentMessages(messages.reverse()); - } - }); + chatState.selectedChatID !== "1" && + getRoomMessagesCall(chatState.selectedChatID, 0, 20).then((res) => { + if (res?.status !== 200 && res?.status !== 201) { + } else { + const messages: Message[] = []; + res.data.forEach( + (message: { + id: string; + avatar: { + thumbnail: string; + medium: string; + large: string; + }; + content: string; + time: string; + roomId: string; + authorId: string; + }) => { + messages.push({ + id: message.id, + avatar: message.avatar, + senderId: message.authorId, + message: message.content, + time: message.time, + }); + } + ); + chatState.fillCurrentMessages(messages.reverse()); + } + }).finally(() => { + setLoading(false); + }); }; - fetch().then(() => { - scrollToBottom(); - }); + fetch(); + return () => { socketStore.socket.off("message", handleMessage); + socketStore.socket.emit("roomDeparture", { + "roomId" : chatState.selectedChatID, + "memberId" : currentUser.id, + "type" : "out" + + }) }; // eslint-disable-next-line }, [chatState.selectedChatID]); @@ -420,11 +452,11 @@ export const Conversation: React.FC = ({ className="flex-grow overflow-y-auto no-scrollbar " ref={messageContainerRef} > - {IsLoading === false ? ( + {IsLoading === false && chatState.selectedChatID !== "1" ? ( (chatState.currentMessages?.length as number) > 0 ? ( chatState.currentMessages?.map((message) => ( { const [isLoading, setLoading] = useState(false); @@ -23,7 +25,7 @@ export const RecentConversations = () => { useEffect(() => { const fetch = async () => { setLoading(true); - await fetchDmsCall(0, 100).then((res) => { + await fetchDmsCall(0, 20).then((res) => { setLoading(false); if (res?.status !== 200 && res?.status !== 201) { } else { @@ -43,6 +45,7 @@ export const RecentConversations = () => { medium: string; large: string; }; + bio: string; }) => { rooms.push({ secondUserId: room.secondMemberId, @@ -53,6 +56,7 @@ export const RecentConversations = () => { content: room.last_message?.content, createdAt: room.last_message?.createdAt, }, + bio: room.bio, }); } ); @@ -118,13 +122,19 @@ export const ChatPlaceHolder = ({ const selectNewChat = useChatStore((state) => state.selectNewChatID); const selectedChatID = useChatStore((state) => state.selectedChatID); const chatState = useChatStore((state) => state); - + const socketStore = useSocketStore(); + const currentUser = useUserStore((state) => state); return (
{ - selectedChatID !== id && selectNewChat(id); + if (selectedChatID !== id) { + // socketStore.socket.emit("joinRoom", { + // memberId: currentUser.id, + // roomId: id, + // }); + selectNewChat(id); + } chatState.setCurrentDmUser({ - // back here secondUserId: secondUserId, id: id, name: username, diff --git a/frontend/code/src/Components/Chat/Components/RoomChatHelpers.tsx b/frontend/code/src/Components/Chat/Components/RoomChatHelpers.tsx index ff45c78..a2e9f56 100644 --- a/frontend/code/src/Components/Chat/Components/RoomChatHelpers.tsx +++ b/frontend/code/src/Components/Chat/Components/RoomChatHelpers.tsx @@ -31,6 +31,7 @@ import { useModalStore } from "../Controllers/LayoutControllers"; import { useUserStore } from "../../../Stores/stores"; import { formatTime } from "./tools/utils"; import { getBlockedCall, unblockCall } from "../Services/FriendsServices"; +import { useSocketStore } from "../Services/SocketsServices"; interface NullComponentProps { message: string; @@ -45,7 +46,7 @@ export const RoomChatPlaceHolder = () => { const fetch = async () => { setIsLoading(true); - await fetchRoomsCall(0, 100, true).then((res) => { + await fetchRoomsCall(0, 20, true).then((res) => { if (res?.status !== 200 && res?.status !== 201) { } else { const rooms: ChatRoom[] = []; @@ -619,6 +620,8 @@ export const RoomSettingsModal = () => { const currentRoom = chatRooms.find((room) => room.id === selectedChatID); const LayoutState = useModalStore((state) => state); const setIsLoading = useChatStore((state) => state.setIsLoading); + const socketStore = useSocketStore(); + const chatState = useChatStore((state) => state); const [currentUsers, setUsers] = useState([]); const [skipCount, setSkipCount] = useState(true); @@ -638,19 +641,19 @@ export const RoomSettingsModal = () => { target: { value: SetStateAction }; }) => { setUpdate(true); - setName(event.target.value); + setName(event.target.value || ''); }; const [selectedOption, setSelectedOption] = useState(RoomType.public); useEffect(() => { setSelectedOption(currentRoom?.type as RoomType); - setName(currentRoom?.name as string); + setName((currentRoom?.name || '') as string); if (skipCount) setSkipCount(false); if (!skipCount) { const fetchData = async () => { try { setLOading(true); - await getRoomMembersCall(currentRoom?.id as string, 0, 30).then( + await getRoomMembersCall(currentRoom?.id as string, 0, 20).then( (res) => { setLOading(false); if (res?.status === 200 || res?.status === 201) { @@ -674,7 +677,7 @@ export const RoomSettingsModal = () => { setUpdate(false); setPassword(""); setSelectedOption(currentRoom?.type as RoomType); - setName(currentRoom?.name as string); + setName((currentRoom?.name || '') as string); }; return ( @@ -824,6 +827,23 @@ export const RoomSettingsModal = () => { res?.status === 200 || res?.status === 201 ) { + if(user.isBaned === true) + { + socketStore.socket.emit("roomDeparture", { + "roomId" : chatState.selectedChatID, + "memberId" : user.id, + + }) + } + else if(user.isBaned === false) + { + socketStore.socket.emit("unban", { + "roomId" : chatState.selectedChatID, + "memberId" : user.id + }) + + } + toast.success(res.data.message); } LayoutState.setShowSettingsModal( @@ -883,6 +903,12 @@ export const RoomSettingsModal = () => { res?.status === 201 ) { toast.success("User Kicked Successfully"); + socketStore.socket.emit("roomDeparture", { + "roomId" : chatState.selectedChatID, + "memberId" : user.id, + "type" : "kick" + + }) } }); }} @@ -1013,7 +1039,7 @@ export const ExploreRoomsModal = () => { useEffect(() => { const fetch = async () => { setIsLoading(true); - await fetchRoomsCall(0, 100, false).then((res) => { + await fetchRoomsCall(0, 20, false).then((res) => { if (res?.status !== 200 && res?.status !== 201) { // toast.error("something went wrong, try again"); resetModalState(); @@ -1258,7 +1284,7 @@ export const ShowLogoModal = () => {
- +

Loading...

diff --git a/frontend/code/src/Components/Chat/Components/UserToUserChat.tsx b/frontend/code/src/Components/Chat/Components/UserToUserChat.tsx index 6692672..ee79c14 100644 --- a/frontend/code/src/Components/Chat/Components/UserToUserChat.tsx +++ b/frontend/code/src/Components/Chat/Components/UserToUserChat.tsx @@ -4,6 +4,8 @@ import { Conversation } from "./Conversation"; import { useParams } from "react-router-dom"; import { useChatStore } from "../Controllers/RoomChatControllers"; +import { getDM } from "../Services/ChatServices"; +import { DmRoom } from "./tools/Assets"; export const UserToUserChat = () => { const params = useParams(); @@ -15,21 +17,26 @@ export const UserToUserChat = () => { useEffect(() => { console.log("selected chat id ", ChatState.selectedChatID); const fetchUser = async () => { - // try { - // await api.get(`profile/${params.id}`).then((res) => { - // console.log(res.data); - // ChatState.setCurrentDmUser({ - // secondUserId: res.data.id, - // id: res.data.id, - // name: `${res.data.firstName} `, - // avatar: { - // thumbnail: res.data.image, - // medium: res.data.image, - // large: res.data.image, - // }, - // }); - // }); - // } catch (error) {} + try { + await getDM(params.id as string).then((res) => { + console.log("res", res); + if (res?.status === 200 || res?.status === 201) { + const extractedData = res.data; + ChatState.setCurrentDmUser({ + id: extractedData.id, + secondUserId: extractedData.secondMemberId, + name: extractedData.name, + avatar: extractedData.avatar, + bio: extractedData.bio, + }); + ChatState.selectNewChatID(extractedData.id); + + console.log("extractedData", extractedData); + } else { + // toast.error("Error getting room members"); + } + }); + } catch (error) {} }; fetchUser(); // eslint-disable-next-line diff --git a/frontend/code/src/Components/Chat/Controllers/RoomChatControllers.tsx b/frontend/code/src/Components/Chat/Controllers/RoomChatControllers.tsx index a90194c..7e93fdd 100644 --- a/frontend/code/src/Components/Chat/Controllers/RoomChatControllers.tsx +++ b/frontend/code/src/Components/Chat/Controllers/RoomChatControllers.tsx @@ -50,7 +50,7 @@ export const useChatStore = create()((set) => ({ currentDmUser: { id: "1", secondUserId: "2", - name: "name", + name: "Loading...", avatar: { thumbnail: "", medium: "", diff --git a/frontend/code/src/Components/Chat/Services/ChatServices.ts b/frontend/code/src/Components/Chat/Services/ChatServices.ts index 5060b45..f6b9740 100644 --- a/frontend/code/src/Components/Chat/Services/ChatServices.ts +++ b/frontend/code/src/Components/Chat/Services/ChatServices.ts @@ -24,6 +24,8 @@ export const createNewRoomCall = async ( }; + + export const updateRoomCall = async ( name: string, type: string, @@ -89,6 +91,22 @@ export const fetchDmsCall = async ( } +export const getDM = async ( + id: string +) => { + try { + const response = await api.get(`/rooms/dm/${id}`, + ); + console.log("dm :"); + console.log(response.status); + console.log(response.data); + return response; + } catch (e: any) { + console.log(e.response.data.message); + } + +} + export const getRoomMembersCall = async ( id: string, @@ -221,3 +239,4 @@ export const getFriendsCall = async ( + diff --git a/frontend/code/src/Components/Chat/index.tsx b/frontend/code/src/Components/Chat/index.tsx index 7d89371..a6e3f7d 100644 --- a/frontend/code/src/Components/Chat/index.tsx +++ b/frontend/code/src/Components/Chat/index.tsx @@ -73,7 +73,7 @@ export const Chat = () => { /> )}
- {chatRooms.length < 1 && ChatState.selectedChatID === "1" ? ( + {chatRooms.length < 1 && ChatState.selectedChatID === "1" ? ( ) : ( @@ -110,7 +110,8 @@ export const UserPreviewCard: React.FC = ({ useEffect(() => { const fetchData = async () => { try { - if (SelectedChat === "1" && selectedChatType === ChatType.Room) { + console.log("selected chat type", selectedChatType); + if (SelectedChat === "1" && selectedChatType !== ChatType.Room) { onRemoveUserPreview(); } else { setIsLoading(true); @@ -187,7 +188,7 @@ export const UserPreviewCard: React.FC = ({

- {currentUser?.id ?? "NO"} + {currentUser?.bio ?? "NO"}

diff --git a/frontend/code/src/Components/FirstLogin/UploadAvatar.tsx b/frontend/code/src/Components/FirstLogin/UploadAvatar.tsx index 1cacd18..1a558ec 100644 --- a/frontend/code/src/Components/FirstLogin/UploadAvatar.tsx +++ b/frontend/code/src/Components/FirstLogin/UploadAvatar.tsx @@ -1,59 +1,116 @@ -import { useForm, SubmitHandler } from "react-hook-form" -import { useUserStore } from "../../Stores/stores" -import api from '../../Api/base' -import { toast } from "react-hot-toast" -import { useNavigate } from "react-router-dom" -import { UploadLogic } from './UploadLogic' +import { useForm, SubmitHandler } from "react-hook-form"; +import { useUserStore } from "../../Stores/stores"; +import api from "../../Api/base"; +import { toast } from "react-hot-toast"; +import { useNavigate } from "react-router-dom"; +import { UploadLogic } from "./UploadLogic"; type Inputs = { Avatar: string; - firstName : string ; - lastName : string ; - discreption : string; - email : string; -} -const ERROR_MESSAGES = ["Field is required" , "Require min length of " , "Passed max length of"] -const payload_objects = ["firstName","lastName","email","phone","discreption"] + firstName: string; + lastName: string; + discreption: string; + email: string; +}; +const ERROR_MESSAGES = [ + "Field is required", + "Require min length of ", + "Passed max length of", +]; +const payload_objects = [ + "firstName", + "lastName", + "email", + "phone", + "discreption", +]; const data_names = ["First name", "Last name", "Email", "Phone", "Bio"]; export const UploadAvatar = () => { const userStore = useUserStore(); const navigate = useNavigate(); - const { - register, - handleSubmit - } = useForm() - const onSubmit: SubmitHandler = async(data) => { - try{ - - toast.promise(api.post("/profile/me",{...data ,finishProfile: true }) , {loading:"Saving user information",success:"Saved successfully",error:"Error on Saving Data"}) - userStore.login(); - navigate("/Home") - }catch(e) - {} - } - const handleError = (errors : any) => { + const { register, handleSubmit } = useForm(); + const onSubmit: SubmitHandler = async (data) => { + try { + toast.promise(api.post("/profile/me", { ...data, finishProfile: true }), { + loading: "Saving user information", + success: "Saved successfully", + error: "Error on Saving Data", + }); + userStore.login(); + navigate("/Home"); + } catch (e) {} + }; + const handleError = (errors: any) => { //eslint-disable-next-line - payload_objects.map((item:any, index : number) =>{ - if (errors[`${item}`]?.type === "required") toast.error(`${data_names[index]} ${ERROR_MESSAGES[0]} `); - if (errors[`${item}`]?.type === "minLength") toast.error(`${data_names[index]} ${ERROR_MESSAGES[1]} 4`); - if (errors[`${item}`]?.type === "maxLength")toast.error(`${data_names[index]} ${ERROR_MESSAGES[2]} 50 `); - }) - -} + payload_objects.map((item: any, index: number) => { + if (errors[`${item}`]?.type === "required") + toast.error(`${data_names[index]} ${ERROR_MESSAGES[0]} `); + if (errors[`${item}`]?.type === "minLength") + toast.error(`${data_names[index]} ${ERROR_MESSAGES[1]} 4`); + if (errors[`${item}`]?.type === "maxLength") + toast.error(`${data_names[index]} ${ERROR_MESSAGES[2]} 50 `); + }); + }; return ( <> -
- - - - - + + + + + + - + - ) -} \ No newline at end of file + ); +}; diff --git a/frontend/code/src/Components/FirstLogin/UploadLogic.tsx b/frontend/code/src/Components/FirstLogin/UploadLogic.tsx index b25ae7a..75cd826 100644 --- a/frontend/code/src/Components/FirstLogin/UploadLogic.tsx +++ b/frontend/code/src/Components/FirstLogin/UploadLogic.tsx @@ -1,47 +1,55 @@ -import {useRef} from 'react' -import { Avatar } from '../Settings/assets/Avatar' -import { Edit } from '../Settings/assets/Edit' -import { useUserStore } from '../../Stores/stores' -import api from '../../Api/base' -import { toast } from 'react-hot-toast' +import { useRef } from "react"; +import { Avatar } from "../Settings/assets/Avatar"; +import { Edit } from "../Settings/assets/Edit"; +import { useUserStore } from "../../Stores/stores"; +import api from "../../Api/base"; +import { toast } from "react-hot-toast"; export const UploadLogic = () => { - const userStore = useUserStore(); - const inputRef = useRef(); - const handleUploadedFile = async(event:any) => { - const h_size = event.target.files[0].size / 1024; - if (h_size > 5120) - { - toast.error("uploded avatar is bigger than 5mb") - return Promise.reject("Error"); - } - const formData = new FormData(); - formData.append("image", event.target.files[0]); - - await api.post("/profile/avatar", formData ,{headers:{"Content-Type":"multipart/form-data"}}); - const res = await api.get("/profile/me") - userStore.setAvatar(res?.data?.picture); - - }; - const handleClick = () => { - inputRef?.current?.click(); - - } - return ( - <> -
- -
- -
+ const userStore = useUserStore(); + const inputRef = useRef(); + const handleUploadedFile = async (event: any) => { + const h_size = event.target.files[0].size / 1024; + if (h_size > 5120) { + toast.error("uploded avatar is bigger than 5mb"); + return Promise.reject("Error"); + } + const formData = new FormData(); + formData.append("image", event.target.files[0]); + + await api.post("/profile/avatar", formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); + const res = await api.get("/profile/me"); + userStore.setAvatar(res?.data?.picture); + }; + const handleClick = () => { + inputRef?.current?.click(); + }; + return ( + <> +
+ +
+
- { - toast.promise(handleUploadedFile(e),{ - loading: "Updating Profile Image", - success:"New Avatar Saved", - error:"Error On Uploading image" - },{position:"top-center",className:"h-20",duration:2000}) - }} ref={inputRef} - /> - - ) -} \ No newline at end of file +
+ { + toast.promise( + handleUploadedFile(e), + { + loading: "Updating Profile Image", + success: "New Avatar Saved", + error: "Error On Uploading image", + }, + { position: "top-center", className: "h-20", duration: 2000 } + ); + }} + ref={inputRef} + /> + + ); +}; diff --git a/frontend/code/src/Components/FirstLogin/index.tsx b/frontend/code/src/Components/FirstLogin/index.tsx index 502d257..abe87db 100644 --- a/frontend/code/src/Components/FirstLogin/index.tsx +++ b/frontend/code/src/Components/FirstLogin/index.tsx @@ -1,21 +1,13 @@ - import { UploadAvatar } from "./UploadAvatar"; -export const FirstLogin = () => { - return ( - <> -
- -
- -
- - -
- - +export const FirstLogin = () => { + return ( +
+
+
+
- - - ); -} +
+
+ ); +}; diff --git a/frontend/code/src/Components/Home/assets/Table.tsx b/frontend/code/src/Components/Home/assets/Table.tsx index 3bf0cbf..6f75938 100644 --- a/frontend/code/src/Components/Home/assets/Table.tsx +++ b/frontend/code/src/Components/Home/assets/Table.tsx @@ -1,139 +1,137 @@ -import { Trophy } from './Trophy' -import { useState,useEffect, useCallback, useRef, FC } from 'react' -import { Daimond } from './Daimond' -import { Loading } from '../../Loading' -import { Link } from 'react-router-dom' -import { NullPlaceHolder } from '../../Chat/Components/RoomChatHelpers' -import { Logo } from '../../Layout/Assets/Logo' -import InfiniteScroll from 'react-infinite-scroll-component'; -import api from '../../../Api/base' -import toast from 'react-hot-toast' +import { Trophy } from "./Trophy"; +import { useState, useEffect, useCallback, useRef, FC } from "react"; +import { Daimond } from "./Daimond"; +import { Loading } from "../../Loading"; +import { Link } from "react-router-dom"; +import { NullPlaceHolder } from "../../Chat/Components/RoomChatHelpers"; +import { Logo } from "../../Layout/Assets/Logo"; +import InfiniteScroll from "react-infinite-scroll-component"; +import api from "../../../Api/base"; +import toast from "react-hot-toast"; -export const Table: FC = () => -{ - const [users, setUsers] = useState([]) - const [loading , setLoading] = useState(true) - const page = useRef(0); - const hasMoreItems = useRef(true) - const [nextPageUrl, setNextPageUrl] : any = useState( +export const Table: FC = () => { + const [users, setUsers] = useState([]); + const [loading, setLoading] = useState(true); + const page = useRef(0); + const hasMoreItems = useRef(true); + const [nextPageUrl, setNextPageUrl]: any = useState( "/leaderboard?offset=0&limit=20" - ); - - const [fetching, setFetching] = useState(false); - const fetchItems = useCallback( - async () => { - if (fetching) { - return; - } + const [fetching, setFetching] = useState(false); - setFetching(true); + const fetchItems = useCallback(async () => { + if (fetching) { + return; + } - try { - const newdata : any = await api.get(nextPageUrl); - if (newdata.data.length < 20) { - setUsers([...users, ...newdata.data]); - setNextPageUrl(null); - return; - } - console.log(newdata.data.length) - if (!newdata.data || newdata.data.length === 0) - { - setNextPageUrl(null) - return ; - } - else - { - console.log(newdata.data) - console.log("here") - setUsers([...users, ...newdata.data]); - setNextPageUrl(`/leaderboard?offset=${page.current}&limit=20`); - page.current += 20; - } + setFetching(true); - + try { + const newdata: any = await api.get(nextPageUrl); + if (newdata.data.length < 20) { + setUsers([...users, ...newdata.data]); + setNextPageUrl(null); + return; } - catch(e) - { - toast.error("Can't get leadeboard"); + console.log(newdata.data.length); + if (!newdata.data || newdata.data.length === 0) { + setNextPageUrl(null); + return; + } else { + console.log(newdata.data); + console.log("here"); + setUsers([...users, ...newdata.data]); + setNextPageUrl(`/leaderboard?offset=${page.current}&limit=20`); + page.current += 20; } - finally { - setLoading(false) + } catch (e) { + toast.error("Can't get leadeboard"); + } finally { + setLoading(false); - setFetching(false); - } - }, - [users, fetching, nextPageUrl] - ); + setFetching(false); + } + }, [users, fetching, nextPageUrl]); useEffect(() => { - fetchItems() + fetchItems(); page.current += 20; //eslint-disable-next-line - },[]) + }, []); hasMoreItems.current = !!nextPageUrl; - - return ( - users?.length > 0 || loading ? ( -
-
} - endMessage={
No more results!
} - hasMore={hasMoreItems.current} - scrollableTarget="scrollTarget" - style={{overflow:"auto", height:"100%"}} - > - - - - - - - - - - - {!loading && users.map((x: any, index: number) => ( + return users?.length > 0 || loading ? ( +
+ + +
+ } + endMessage={ +
+ No more results! +
+ } + hasMore={hasMoreItems.current} + scrollableTarget="scrollTarget" + style={{ overflow: "auto", height: "100%" }} + > +
PlaceUserScore
+ + + + + + + + + {!loading && + users.map((x: any, index: number) => ( - - + + - - - ))} - {loading && ()} - -
PlaceUserScore
-
+
{index + 1}
+ -
-
- Avatar Tailwind CSS Component
-
-
{x?.Username}
-
+
+
+
+ Avatar Tailwind CSS Component{" "} +
+
+
+ {x?.Username} +
+
-
{x?.wins}
+
{x?.wins}
+
- -
- ):( -
- -
- ) - - ); -} \ No newline at end of file + ))} + {loading && } + + + +
+ ) : ( +
+ +
+ ); +}; diff --git a/frontend/code/src/Components/Home/index.tsx b/frontend/code/src/Components/Home/index.tsx index 808709d..87ddf2d 100644 --- a/frontend/code/src/Components/Home/index.tsx +++ b/frontend/code/src/Components/Home/index.tsx @@ -1,31 +1,29 @@ -import { Button } from './assets/Button' -import { LeaderBoard } from './LeaderBoard' -import { Link } from 'react-router-dom' -import herosvg from './assets/Hero.png' +import { Button } from "./assets/Button"; +import { LeaderBoard } from "./LeaderBoard"; +import { Link } from "react-router-dom"; +import herosvg from "./assets/Hero.png"; -export const Home = () : JSX.Element =>{ - - return ( - <> - -
-
- leaderboard hero -
- -
- +export const Home = (): JSX.Element => { + return ( +
+
+ leaderboard hero + +
+
+ READY TO PLAY A GAME?{" "} +
+
- -
- - - - ) -} \ No newline at end of file +
+ +
+
+ ); +}; diff --git a/frontend/code/src/Components/Layout/Assets/Avatar.tsx b/frontend/code/src/Components/Layout/Assets/Avatar.tsx index 7a9d6f3..6e475ed 100644 --- a/frontend/code/src/Components/Layout/Assets/Avatar.tsx +++ b/frontend/code/src/Components/Layout/Assets/Avatar.tsx @@ -1,24 +1,43 @@ -import { Link } from "react-router-dom"; -import './style.css' +import "./style.css"; + +import { Link } from "react-router-dom"; + import { useUserStore } from "../../../Stores/stores"; -export const Avatar = (props:any) =>{ - const user = useUserStore(); - return ( -
-
- profile -
-
    -
  • Settings
  • -
  • Profile
  • - { - process.env.REACT_APP_LOGOUT && - user.logout()} to={process.env.REACT_APP_LOGOUT}>
  • Logout
  • - } -
-
- - - - ) -} + +type AvatarProps = { + picture: string; +}; + +export const Avatar = (props: AvatarProps) => { + const user = useUserStore(); + + return ( +
+
+ profile picture +
+
    + +
  • +
    Settings
    +
  • + + +
  • +
    Profile
    +
  • + + {process.env.REACT_APP_LOGOUT && ( + user.logout()} to={process.env.REACT_APP_LOGOUT}> +
  • +
    Logout
    +
  • + + )} +
+
+ ); +}; diff --git a/frontend/code/src/Components/Layout/Assets/Logo.tsx b/frontend/code/src/Components/Layout/Assets/Logo.tsx index 7231c48..702a374 100644 --- a/frontend/code/src/Components/Layout/Assets/Logo.tsx +++ b/frontend/code/src/Components/Layout/Assets/Logo.tsx @@ -1,26 +1,60 @@ -import { Link } from "react-router-dom" -type sizes = { - x:string; - y:string -} -export const Logo = ({x,y}:sizes ) => { - return ( - -
- - - - - - - - - - - - - -
- - ) -} \ No newline at end of file +import { Link } from "react-router-dom"; + +export const Logo = ({ className }: React.HTMLAttributes) => { + return ( + +
+ + + + + + + + + + + + + +
+ + ); +}; diff --git a/frontend/code/src/Components/Layout/index.tsx b/frontend/code/src/Components/Layout/index.tsx index 215194e..cc80e47 100644 --- a/frontend/code/src/Components/Layout/index.tsx +++ b/frontend/code/src/Components/Layout/index.tsx @@ -8,7 +8,7 @@ import { Message } from "./Assets/Message"; import { Profile } from "./Assets/Profile"; import { Settings } from "./Assets/Settings"; import { Out } from "./Assets/Out"; -import { FC, PropsWithChildren, useLayoutEffect} from "react"; +import { FC, PropsWithChildren, useLayoutEffect } from "react"; import { Outlet } from "react-router"; import { matchRoutes, useLocation } from "react-router-dom"; import { useUserStore } from "../../Stores/stores"; @@ -40,35 +40,33 @@ function onConnect() { console.log("hello"); } export const Layout: FC = (): JSX.Element => { - const user = useUserStore(); const navigate = useNavigate(); const socketStore = useSocketStore(); - + useLayoutEffect(() => { const log = async () => { try { await user.login(); - } - catch(e:any){ - if (e?.response?.status !== 403 && e?.response?.data?.message !== "Please complete your profile") - { + } catch (e: any) { + if ( + e?.response?.status !== 403 && + e?.response?.data?.message !== "Please complete your profile" + ) { navigate("/"); user.logout(); - } + } } - - }; - + socketStore.socket = socketStore.setSocket(); socketStore.socket.on("connect", onConnect); - - socketStore.socket.on("message",(msg:any) => { + + socketStore.socket.on("message", (msg: any) => { toast.custom((t) => (
@@ -99,8 +97,8 @@ export const Layout: FC = (): JSX.Element => {
- )) - }) + )); + }); log(); return () => { socketStore.socket.off("connect", onConnect); @@ -109,21 +107,21 @@ export const Layout: FC = (): JSX.Element => { }, []); const path: string = useCurrentPath(); - const obj = { x: "30", y: "20" }; + return ( <> {user.profileComplet === false && user.isLogged ? ( ) : (
- +
-
- - +
+ +
@@ -164,9 +162,11 @@ export const Layout: FC = (): JSX.Element => {
-
- - +
+
diff --git a/frontend/code/src/Components/Loading/index.tsx b/frontend/code/src/Components/Loading/index.tsx index 7a79085..16dd0e1 100644 --- a/frontend/code/src/Components/Loading/index.tsx +++ b/frontend/code/src/Components/Loading/index.tsx @@ -1,15 +1,15 @@ -import { Logo } from '../Layout/Assets/Logo' -export const Loading = (size:any) =>{ - return ( - - ) -} +import { Logo } from "../Layout/Assets/Logo"; +export const Loading = (size: any) => { + return ( + + + + ); +}; export const Load = () => { - return (
) - -} + return
; +}; export const PageLoading = () => { - const obj = {x:"72",y:"72"} - return () -} \ No newline at end of file + return ; +}; diff --git a/frontend/code/src/Components/Login/index.tsx b/frontend/code/src/Components/Login/index.tsx index 1e2be22..4df614e 100644 --- a/frontend/code/src/Components/Login/index.tsx +++ b/frontend/code/src/Components/Login/index.tsx @@ -28,7 +28,7 @@ export const Login = () => console.log("hit") } else { // Handle other errors - console.error(error); + // console.error(error); } } } diff --git a/frontend/code/src/Components/Profile/assets/Table.tsx b/frontend/code/src/Components/Profile/assets/Table.tsx index d970b58..b38cb81 100644 --- a/frontend/code/src/Components/Profile/assets/Table.tsx +++ b/frontend/code/src/Components/Profile/assets/Table.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef } from "react"; import { Link } from "react-router-dom"; -import { Loading } from "../../Loading/"; +import { Loading } from "../../Loading/"; import { Logo } from "../../Layout/Assets/Logo"; import InfiniteScroll from "react-infinite-scroll-component"; import { NullPlaceHolder } from "../../Chat/Components/RoomChatHelpers"; @@ -72,7 +72,7 @@ export const Table = (props: any) => { hasMore={hasMore} loader={
- +
} dataLength={history.length} @@ -137,14 +137,12 @@ export const Table = (props: any) => { />{" "} ) : ( - )}
{x?.match?.Player2?.username ? ( x.match.Player2.username ) : ( - )}{" "}
diff --git a/frontend/code/src/Components/Profile/index.tsx b/frontend/code/src/Components/Profile/index.tsx index bcf414d..9f19994 100644 --- a/frontend/code/src/Components/Profile/index.tsx +++ b/frontend/code/src/Components/Profile/index.tsx @@ -1,6 +1,5 @@ import { Pong } from "./assets/Pong"; - import { History } from "./History"; import Hero from "./assets/Hero.gif"; import { useState, useEffect } from "react"; @@ -8,7 +7,7 @@ import { Link, useNavigate, useParams } from "react-router-dom"; import { Load } from "../Loading/"; import Newbie from "../Badges/Newbie.svg"; import Master from "../Badges/Master.svg"; -import Ultimate from "../Badges/Ultimate.svg" +import Ultimate from "../Badges/Ultimate.svg"; import { useUserStore } from "../../Stores/stores"; import { VscChromeClose, @@ -258,13 +257,13 @@ export const Profile = () => { ChatState.changeChatType(ChatType.Chat); ChatState.selectNewChatID(res?.data?.id); ChatState.setCurrentDmUser({ - secondUserId : profile.id, + secondUserId: profile.id, id: profile.id, name: `${profile.name.first} `, avatar: profile?.picture, bio: profile?.bio, }); - navigate(`/Dm/${params.id}`); + navigate(`/Dm/${res?.data.id}`); } else { toast.error( "You Can't Send Message To this User For Now, try Again later" diff --git a/frontend/code/src/Components/Settings/assets/Avatar.tsx b/frontend/code/src/Components/Settings/assets/Avatar.tsx index e72b811..cfd6a49 100644 --- a/frontend/code/src/Components/Settings/assets/Avatar.tsx +++ b/frontend/code/src/Components/Settings/assets/Avatar.tsx @@ -1,9 +1,9 @@ -export const Avatar = (props:any) => { - return ( -
-
- avatar -
-
- ) -} \ No newline at end of file +export const Avatar = (props: any) => { + return ( +
+
+ avatar +
+
+ ); +}; diff --git a/frontend/code/src/Components/Settings/assets/Inputs.tsx b/frontend/code/src/Components/Settings/assets/Inputs.tsx index f20ce7e..64f175d 100644 --- a/frontend/code/src/Components/Settings/assets/Inputs.tsx +++ b/frontend/code/src/Components/Settings/assets/Inputs.tsx @@ -1,150 +1,179 @@ -import { useSpring, animated } from '@react-spring/web' -import { BsFillCheckCircleFill , BsFillXCircleFill } from 'react-icons/bs' -import { Edit } from './Edit'; -import { useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { useUserStore } from '../../../Stores/stores' -import toast from 'react-hot-toast'; -import api from '../../../Api/base'; -interface MyComponentProps { - name: string; - data:string; - payload: string; - } - const ERROR_MESSAGES = ["Field is required" , "Require min length of " , "Passed max length of"] +import type { FieldErrors, FieldValues } from "react-hook-form"; - const postData :any = async(data : any , payload : any) : Promise => { - - const key = payload; // Replace with your actual payload key - const value = data[payload]; // Replace with your actual payload value - - const ndata = { - [key]: value, - }; - - const res = await api.post("/profile/me",{...ndata}) - console.log(res.data) - return res.data.message - - } -export const Inputs = (props:MyComponentProps) => { - var payload = props.payload - const user = useUserStore(); - //eslint-disable-next-line - const { register, handleSubmit, reset, formState: {errors} } = useForm(); - const handleError = (errors : any) => { - console.log(errors) - if (errors[`${props.payload}`]?.type === "required") toast.error(`${props.name} ${ERROR_MESSAGES[0]} `); - if (errors[`${props.payload}`]?.type === "minLength") toast.error(`${props.name} ${ERROR_MESSAGES[1]} 4`); - if (errors[`${props.payload}`]?.type === "maxLength")toast.error(`${props.name} ${ERROR_MESSAGES[2]} 50 `); - if (errors[`email`].type === "pattern")toast.error(`${errors['email'].message}`); - } - const onSubmit = (data : any ) => { - toast.promise( - - postData(data, payload), - { - - loading: 'Saving...', - success: {props.name} saved, - error: could not save {this}, - - }, - {className:"font-poppins font-bold relative top-[6vh] bg-base-100 text-white"} - ) - switch (props.name) - { - case "First name" : user.updateFirstName(data[`${props.payload}`]); - break; - case "Last name" : user.updateLastName(data[`${props.payload}`]); - break; - case "Email" : user.updateEmail(data[`${props.payload}`]); - break; - case "Phone" : user.updatePhone(data[`${props.payload}`]); - break; - case "Bio" : user.updateBio(data[`${props.payload}`]); - break; - } - - }; - - const [input, setInput] = useState(false) - const [springs ,api] = useSpring(() => ({ - from: { x: '0%' , opacity:100}, +import { useSpring, animated } from "@react-spring/web"; +import { BsFillCheckCircleFill, BsFillXCircleFill } from "react-icons/bs"; +import { Edit } from "./Edit"; +import { useCallback, useState } from "react"; +import { useForm } from "react-hook-form"; +import { useUserStore } from "../../../Stores/stores"; +import toast from "react-hot-toast"; +import api from "../../../Api/base"; - })) - const handleNameClick = () =>{ - if (input === false){ - api.start({ - to: { - x: springs.x.get() === '20%' ? '0' : '20%', - }, +import { classNames } from "../../../Utils/helpers"; - }) - setInput(true) - } - else { - api.start({ - to: { - x: springs.x.get() === '20%' ? '0' : '20%', - }, - - }) - setInput(false) - } - } +const postData: any = async (data: any, payload: any): Promise => { + const key = payload; // Replace with your actual payload key + const value = data[payload]; // Replace with your actual payload value + + const res = await api.post("/profile/me", { [key]: value }); + return res.data.message; +}; + +interface InputsProps { + name: string; + data: string; + payload: string; + // Optional + className?: string; + containerClassName?: string; +} + +export const Inputs = (props: InputsProps) => { + const user = useUserStore(); + + const { register, handleSubmit, reset } = useForm(); - const handleCancle = () => { - toast.error("Task cancled",{className:"font-poppins font-bold relative top-[6vh] bg-base-100 text-white"}); - reset() + const onSubmit = (data: Record) => { + // Update field on the server + const toastPromise = toast.promise( + postData(data, props.payload), + { + loading: "Saving...", + success: {props.name} saved, + error: could not save {this}, + }, + { + className: + "font-poppins font-bold relative top-[6vh] bg-base-100 text-white", + } + ); + // Update field locally after a successful request + toastPromise.then(() => { + switch (props.name) { + case "First name": + user.updateFirstName(data[`${props.payload}`]); + break; + case "Last name": + user.updateLastName(data[`${props.payload}`]); + break; + case "Email": + user.updateEmail(data[`${props.payload}`]); + break; + case "Phone": + user.updatePhone(data[`${props.payload}`]); + break; + case "Bio": + user.updateBio(data[`${props.payload}`]); + break; + } + }); + }; + + const handleError = useCallback((errors: FieldErrors) => { + if (errors[props.payload]?.type === "required") + toast.error(`${props.name} Field is required`); + if (errors[props.payload]?.type === "minLength") + toast.error(`${props.name} Require min length of 4`); + if (errors[props.payload]?.type === "maxLength") + toast.error(`${props.name} Passed max length of 50`); + // Additional error for "email" field + if (props.payload === "email" && errors[props.payload]?.type === "pattern") + toast.error(`${errors[props.payload]?.message}`); + }, []); + + const handleCancel = useCallback(() => { + toast.error("Task canceled", { + className: + "font-poppins font-bold relative top-[6vh] bg-base-100 text-white", + }); + reset(); + }, []); + + const [input, setInput] = useState(false); + const [springs, springsRef] = useSpring(() => ({ + from: { x: "0%", opacity: 100 }, + })); + + const handleNameClick = () => { + if (input === false) { + springsRef.start({ to: { x: springs.x.get() === "20%" ? "0%" : "20%" } }); + setInput(true); + } else { + springsRef.start({ to: { x: springs.x.get() === "20%" ? "0%" : "20%" } }); + setInput(false); } - return ( -
-
{props.name}
-
-
-
- { - props.name === "Email" ? ( - - ): - ( - + }; - ) - } -
- {!input && - + return ( +
+
{props.name}
+
+ +
+ {props.name === "Email" ? ( + + ) : ( + + )} +
+ {!input && ( + + - } - { - input && -
- - - -
- -
- } - -
-
- - ) -} \ No newline at end of file + )} + {input && ( + +
+ + +
+
+ )} + +
+
+ ); +}; diff --git a/frontend/code/src/Components/Settings/index.tsx b/frontend/code/src/Components/Settings/index.tsx index 4ece37e..e90560c 100644 --- a/frontend/code/src/Components/Settings/index.tsx +++ b/frontend/code/src/Components/Settings/index.tsx @@ -1,14 +1,27 @@ - import Newbie from "../Badges/Newbie.svg"; import Master from "../Badges/Master.svg"; import Ultimate from "../Badges/Ultimate.svg"; -import api from "../../Api/base"; +import api from "../../Api/base"; import toast from "react-hot-toast"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useMemo } from "react"; import { Inputs } from "./assets/Inputs"; import { useUserStore } from "../../Stores/stores"; import { UploadLogic } from "../FirstLogin/UploadLogic"; + +import QRCode from "react-qr-code"; + +import appStoreIcon from "../images/app-store.svg"; +import playStoreIcon from "../images/play-store.svg"; +import googleAuthenticatorIcon from "../images/google-authenticator.svg"; + export const Setting = () => { + const TOTPSecretKey = useMemo( + () => btoa(Math.random().toString(36)).substring(0, 16), + [] + ); + + const [TOTPCode, setTOTPCode] = useState(""); + const getdata: any = async () => { const data: any = await api.get("/test"); return data; @@ -16,15 +29,9 @@ export const Setting = () => { const user = useUserStore(); const [myuser, setMyuser] = useState(user); - const data_names = ["First name", "Last name", "Email", "Phone", "Bio"]; - const payload_objects = ["firstName","lastName","email","phone","discreption"] - const data_content = [ - user.name.first, - user.name.last, - user.email, - user.phone, - user.bio, - ]; + const data_names = ["First name", "Last name", "Email", "Bio"]; + const payload_objects = ["firstName", "lastName", "email", "discreption"]; + const data_content = [user.name.first, user.name.last, user.email, user.bio]; useEffect(() => { setMyuser(user); @@ -32,30 +39,28 @@ export const Setting = () => { return ( <> -
-

+
+

Profile Settings

-
-

change preview

-
-
+
+

change preview

+
+
-
- +
+
- {" "} - {myuser.name.first} {myuser.name.last}{" "} + {myuser.name.first} {myuser.name.last}
- {" "} {user.bio}
-
+
{ toast.promise(getdata(), { @@ -84,28 +89,187 @@ export const Setting = () => {
-

change preview

-
-
-
- {data_names.map((x, index) => { - return ( - - ); - })} -
-

- How to Enable two factor Auth -

-
-
-
-
+

change preview

+
+
+
+ {data_names.map((x, index) => ( +
+
+ ))} +
+
+
+ +

change preview

+
+
+
+
+ {user.tfa === false ? ( +
+

+ How to Enable two factor Auth +

+
+
+ + Install Google Auth + + +
+ + +
+
+
+ + Scan the QR + + + + {TOTPSecretKey} + +
+
+ + Verify your device + +
+ + Enter your code + + { + if ( + (event.key >= "0" && event.key <= "9") || + event.key === "Backspace" + ) + return true; + event.preventDefault(); + return false; + }} + value={TOTPCode} + onChange={(event) => { + const value = event.target.value; + setTOTPCode(value || ""); + }} + /> +
+ +
+
+
+ ) : ( +
+

+ How to Disable two factor Auth +

+
+
+ + Verify your device + +
+ + Enter your code + + { + if ( + (event.key >= "0" && event.key <= "9") || + event.key === "Backspace" + ) + return true; + event.preventDefault(); + return false; + }} + value={TOTPCode} + onChange={(event) => { + const value = event.target.value; + setTOTPCode(value || ""); + }} + /> +
+ +
+
+
+ )}
+ +
diff --git a/frontend/code/src/Components/Validate2Fa/index.tsx b/frontend/code/src/Components/Validate2Fa/index.tsx new file mode 100644 index 0000000..477a132 --- /dev/null +++ b/frontend/code/src/Components/Validate2Fa/index.tsx @@ -0,0 +1,85 @@ +import { useParams, useNavigate } from "react-router-dom"; +import { useState, useEffect } from "react"; +import api from "../../Api/base"; +import toast from "react-hot-toast"; +import { classNames } from "../../Utils/helpers"; + +export const Validate2Fa = () => { + const params = useParams(); + const [isLoading, setIsLoading] = useState(true); + const [TOTPCode, setTOTPCode] = useState(""); + const navigate = useNavigate(); + + console.log(`params : ${params.token} type ${typeof params.token}`); + useEffect(() => { + api.get(`/auth/validatToken/${params.token}`).then((res) => { + if (!res.data) + { + navigate("/") + } + else + setIsLoading(false); + }).catch((Err) => console.log(Err)) + + //eslint-disable-next-line + }, []) + return ( +
+
+
+
+ + Verify your device + +
+ Enter your code + { + if ( + (event.key >= "0" && event.key <= "9") || + event.key === "Backspace" + ) + return true; + event.preventDefault(); + return false; + }} + value={TOTPCode} + onChange={(event) => { + const value = event.target.value; + setTOTPCode(value || ""); + }} + /> +
+ +
+
+
+
+ ); +}; diff --git a/frontend/code/src/Components/images/app-store.svg b/frontend/code/src/Components/images/app-store.svg new file mode 100644 index 0000000..9544a2b --- /dev/null +++ b/frontend/code/src/Components/images/app-store.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/code/src/Components/images/google-authenticator.svg b/frontend/code/src/Components/images/google-authenticator.svg new file mode 100644 index 0000000..153c098 --- /dev/null +++ b/frontend/code/src/Components/images/google-authenticator.svg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/code/src/Components/images/play-store.svg b/frontend/code/src/Components/images/play-store.svg new file mode 100644 index 0000000..f67497d --- /dev/null +++ b/frontend/code/src/Components/images/play-store.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/code/src/Routes/index.tsx b/frontend/code/src/Routes/index.tsx index 5c08948..d1a779b 100644 --- a/frontend/code/src/Routes/index.tsx +++ b/frontend/code/src/Routes/index.tsx @@ -74,6 +74,13 @@ const router = createBrowserRouter([ } ], }, + { + path: "2fa/validate/:token", + lazy: async () => { + let { Validate2Fa } = await import("../Components/Validate2Fa"); + return { Component: Validate2Fa }; + }, + }, { path: "*", lazy: async () => { diff --git a/frontend/code/src/Stores/stores.ts b/frontend/code/src/Stores/stores.ts index 32efd36..f8e8b4e 100644 --- a/frontend/code/src/Stores/stores.ts +++ b/frontend/code/src/Stores/stores.ts @@ -1,6 +1,6 @@ import { create } from "zustand"; import { persist, createJSONStorage } from "zustand/middleware"; -import api from "../Api/base"; +import api from "../Api/base"; export type State = { isLogged: boolean; id: string; @@ -21,7 +21,7 @@ export type State = { banListIds: string[]; achivments: number[]; dmsIds: string[]; - profileComplet:boolean; + profileComplet: boolean; history: | [ { @@ -43,74 +43,77 @@ export type State = { }; type Action = { - - login: () => Promise; - logout: () => void; - - toggleTfa: (tfa: State["tfa"]) => void; + login: () => Promise; + logout: () => void; + fetchNotifications: ( + offset: number, + limit: number + ) => Promise[]>; + + toggleTfa: () => void; updateFirstName: (firstName: State["name"]["first"]) => void; updateLastName: (lastName: State["name"]["last"]) => void; updateEmail: (email: State["email"]) => void; updatePhone: (phone: State["phone"]) => void; updateBio: (bio: State["bio"]) => void; - setAvatar : (picture : State['picture']) =>void; + setAvatar: (picture: State["picture"]) => void; }; - export const useUserStore = create()( - persist((set) => ({ - isLogged: false, - id: "", - bio: "", - phone: "", - name: { - first: "", - last: "", - }, - picture: { - thumbnail: "", - medium: "", - large: "", - }, - email: "", - tfa: false, - friendListIds: [], - banListIds: [], - achivments: [], - dmsIds: [], - history: [], - chatRoomsJoinedIds: [], - profileComplet:false, - toggleTfa: (tfa) => set(() => ({ tfa: !tfa })), - updateFirstName: (firstName) => - set((state) => ({ - name: { - ...state.name, - first: firstName, - }, - })), - updateLastName: (lastName: string) => - set((state) => ({ - name: { - ...state.name, - last: lastName, - }, - })), - updateEmail: (email: string) => - set(() => ({ - email: email, - })), - updatePhone: (phone: State["phone"]) => set(() => ({ phone: phone })), - updateBio: (bio: State["bio"]) => set(() => ({ bio: bio })), - setAvatar : (picture : State['picture']) => set(() => ({picture:picture})), + persist( + (set, get) => ({ + isLogged: false, + id: "", + bio: "", + phone: "", + name: { + first: "", + last: "", + }, + picture: { + thumbnail: "", + medium: "", + large: "", + }, + email: "", + tfa: false, + friendListIds: [], + banListIds: [], + achivments: [], + dmsIds: [], + history: [], + chatRoomsJoinedIds: [], + profileComplet: false, + toggleTfa: () => set(({ tfa }) => ({ tfa: !tfa })), + updateFirstName: (firstName) => + set((state) => ({ + name: { + ...state.name, + first: firstName, + }, + })), + updateLastName: (lastName: string) => + set((state) => ({ + name: { + ...state.name, + last: lastName, + }, + })), + updateEmail: (email: string) => + set(() => ({ + email: email, + })), + updatePhone: (phone: State["phone"]) => set(() => ({ phone: phone })), + updateBio: (bio: State["bio"]) => set(() => ({ bio: bio })), + setAvatar: (picture: State["picture"]) => + set(() => ({ picture: picture })), login: async () => { const res = await api.get("/profile/me"); var user_data = res.data; // user_data.picture= null - const check = user_data.picture.large.split`/` - if (check[check.length - 1] === "null") - user_data.picture = null; - const userInitialValue :State= { + const check = user_data.picture.large.split`/`; + if (check[check.length - 1] === "null") user_data.picture = null; + const userInitialValue: State = { isLogged: true, id: user_data.id, bio: user_data?.bio ?? "default bio", @@ -121,34 +124,49 @@ export const useUserStore = create()( last: user_data.name.last, }, picture: { - thumbnail: user_data?.picture?.thumbnail ?? `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, - medium: user_data?.picture?.medium ?? `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, - large: user_data?.picture?.large ?? `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, + thumbnail: + user_data?.picture?.thumbnail ?? + `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, + medium: + user_data?.picture?.medium ?? + `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, + large: + user_data?.picture?.large ?? + `https://ui-avatars.com/api/?name=${user_data.name.first}-${user_data.name.last}&background=7940CF&color=fff`, }, email: user_data.email, - tfa: false, + tfa: user_data.tfa, friendListIds: [], banListIds: [], achivments: [], dmsIds: [], history: [], chatRoomsJoinedIds: [], - profileComplet:user_data.profileFinished, + profileComplet: user_data.profileFinished, }; // console.log(userInitialValue) + const state = get(); + const notifs = await state.fetchNotifications(0, 20); set({ ...userInitialValue }); - return userInitialValue.isLogged - - + return userInitialValue.isLogged; }, logout: () => { - set({},true); - }, - - }), - { - name: "userStore", - storage : createJSONStorage(() => localStorage) as any, -} -)) \ No newline at end of file + set({}, true); + }, + + fetchNotifications: async (offset: number, limit: number) => { + const response = await api.get( + `/profile/notifications/?offset=${offset}&limit=${limit}`, + { params: { offset, limit } } + ); + console.log("notifications:", response.data); + return []; + }, + }), + { + name: "userStore", + storage: createJSONStorage(() => localStorage) as any, + } + ) +); diff --git a/frontend/code/src/Utils/helpers.ts b/frontend/code/src/Utils/helpers.ts index 3367d5a..a2f6da7 100644 --- a/frontend/code/src/Utils/helpers.ts +++ b/frontend/code/src/Utils/helpers.ts @@ -1,4 +1,5 @@ +import { twMerge } from "tailwind-merge"; + export function classNames(...args: (string | number | boolean | undefined)[]) { - return args.filter(Boolean).map(String).join(' '); + return twMerge(...args.filter(Boolean).map(String)); } - diff --git a/frontend/code/src/logo.svg b/frontend/code/src/logo.svg deleted file mode 100644 index 9dfc1c0..0000000 --- a/frontend/code/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/frontend/code/tsconfig.json b/frontend/code/tsconfig.json index 62dab2b..8046ff8 100644 --- a/frontend/code/tsconfig.json +++ b/frontend/code/tsconfig.json @@ -19,7 +19,6 @@ "isolatedModules": true, "noEmit": true, "jsx": "preserve", - }, "include": [ "src"