From e068d1c0b7d90265ab0370b70b04c8f20a7385fc Mon Sep 17 00:00:00 2001 From: Smachy Date: Tue, 5 Nov 2024 11:47:59 +0100 Subject: [PATCH 001/118] feat: add .env example --- .env | 1 + .env.example | 1 + 2 files changed, 2 insertions(+) create mode 100644 .env create mode 100644 .env.example diff --git a/.env b/.env new file mode 100644 index 0000000..9a69c24 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PORT=5000 \ No newline at end of file diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..9a69c24 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +PORT=5000 \ No newline at end of file From 4fc36f4813ff8beebd8687916747a4cf5d6c5e63 Mon Sep 17 00:00:00 2001 From: Smachy Date: Tue, 5 Nov 2024 13:49:27 +0100 Subject: [PATCH 002/118] fix: delete env file --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 4d29575..e9c0b77 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # misc .DS_Store +.env .env.local .env.development.local .env.test.local @@ -21,3 +22,5 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + + From 0b18b0e9b2b9e9a2d844a42fcc565d4e37ecae29 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 13:51:28 +0100 Subject: [PATCH 003/118] :see_no_evil: .env in gitignore --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e9c0b77..27c524a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,5 +22,4 @@ npm-debug.log* yarn-debug.log* yarn-error.log* - - +.env From 108918a4dd051ea1491c5c78c04c0b19eeb33346 Mon Sep 17 00:00:00 2001 From: Diego <108872676+Smachy24@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:53:41 +0100 Subject: [PATCH 004/118] Delete .env --- .env | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .env diff --git a/.env b/.env deleted file mode 100644 index 9a69c24..0000000 --- a/.env +++ /dev/null @@ -1 +0,0 @@ -PORT=5000 \ No newline at end of file From 7a4b9d7988f143f3fe1ae29f2144151608820d4f Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 16:08:10 +0100 Subject: [PATCH 005/118] feat : User Register --- src/pages/RegisterPage.tsx | 66 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/pages/RegisterPage.tsx diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx new file mode 100644 index 0000000..fca1b90 --- /dev/null +++ b/src/pages/RegisterPage.tsx @@ -0,0 +1,66 @@ +import { useState } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; + +type Inputs = { + username: string; + password: string; +}; + +export default function RegisterPage() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const [resError, setResError] = useState(); + + const navigate = useNavigate(); + + const onSubmit: SubmitHandler = async (data) => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/register`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + } + ); + if (!response.ok) { + const { message } = await response.json(); + setResError(message); + return; + } + + navigate("/login"); + }; + + return ( + <> +

Inscription

+ +
+ + + {resError && {resError}} + +
+ + + + ); +} From ac38da7379a182a56a96c64f38397aa81ab01746 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 16:08:41 +0100 Subject: [PATCH 006/118] feat: User login --- src/pages/LoginPage.tsx | 90 +++++++++++++++++++++++++++++++++++++++++ src/stores/userStore.ts | 21 ++++++++++ 2 files changed, 111 insertions(+) create mode 100644 src/pages/LoginPage.tsx create mode 100644 src/stores/userStore.ts diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx new file mode 100644 index 0000000..2a3fd94 --- /dev/null +++ b/src/pages/LoginPage.tsx @@ -0,0 +1,90 @@ +import { useState } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { useNavigate } from "react-router-dom"; +import { useUserStore } from "../stores/userStore"; + +type Inputs = { + username: string; + password: string; +}; + +export default function LoginPage() { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const [resError, setResError] = useState(); + const navigate = useNavigate(); + + const onSubmit: SubmitHandler = async (data) => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/login`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + } + ); + if (!response.ok) { + const { message } = await response.json(); + setResError(message); + return; + } + + setUserStore(); + }; + + const updateUserId = useUserStore((state) => state.updateUserId); + const updateUsername = useUserStore((state) => state.updateUsername); + + const setUserStore = async () => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/me`, + { + credentials: "include", + } + ); + + if (!response.ok) { + const { message } = await response.json(); + setResError(message); + return; + } + + const user = await response.json(); + updateUserId(user.id); + updateUsername(user.username); + }; + + return ( + <> +

Connexion

+ +
+ + + {resError && {resError}} + +
+ + + + ); +} diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts new file mode 100644 index 0000000..11d60d9 --- /dev/null +++ b/src/stores/userStore.ts @@ -0,0 +1,21 @@ +import { create } from "zustand"; + +type State = { + username: string; + userId: string; +}; + +type Action = { + clearUsername: () => void; + clearUserId: () => void; + updateUsername: (newUsername: string) => void; + updateUserId: (newUserId: string) => void; +}; +export const useUserStore = create((set) => ({ + username: "", + userId: "", + clearUsername: () => set({ username: "" }), + clearUserId: () => set({ userId: "" }), + updateUsername: (newUsername: string) => set({ username: newUsername }), + updateUserId: (newUserId: string) => set({ userId: newUserId }), +})); From 80ff3508e24b6f452c6b9b94539cfb21b0436be0 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 16:09:37 +0100 Subject: [PATCH 007/118] feat : Add Login and Register to the router --- src/index.tsx | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/index.tsx b/src/index.tsx index 69d9e6a..ca9c982 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,6 +7,8 @@ import ChatPage from "./pages/ChatPage"; import FriendPage from "./pages/FriendPage"; import App from "./App"; import "./index.css"; +import RegisterPage from "./pages/RegisterPage"; +import LoginPage from "./pages/LoginPage"; const router = createBrowserRouter([ { @@ -27,6 +29,14 @@ const router = createBrowserRouter([ }, ], }, + { + path: "/register", + element: , + }, + { + path: "/login", + element: , + }, ]); const root = ReactDOM.createRoot( From ce6acf0e408f3331b2608e2114340ff47479c552 Mon Sep 17 00:00:00 2001 From: Smachy Date: Tue, 5 Nov 2024 16:13:36 +0100 Subject: [PATCH 008/118] feat: send message to user --- package-lock.json | 47 +++++++++++++++++++- package.json | 4 +- src/index.tsx | 13 ++++-- src/pages/ChatListPage.tsx | 45 ++++++++++++++++++++ src/pages/ChatPage.tsx | 87 ++++++++++++++++++++------------------ src/stores/messageStore.ts | 11 +++++ src/types/chat.ts | 7 +++ src/types/message.ts | 3 ++ 8 files changed, 172 insertions(+), 45 deletions(-) create mode 100644 src/pages/ChatListPage.tsx create mode 100644 src/stores/messageStore.ts create mode 100644 src/types/chat.ts create mode 100644 src/types/message.ts diff --git a/package-lock.json b/package-lock.json index b4446c4..851a01e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,12 @@ "@types/react-dom": "^18.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.1" }, "devDependencies": { "tailwindcss": "^3.4.14" @@ -13906,6 +13908,21 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "license": "MIT" }, + "node_modules/react-hook-form": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz", + "integrity": "sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -17472,6 +17489,34 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index b9c508a..e99c9ce 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "@types/react-dom": "^18.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/src/index.tsx b/src/index.tsx index 69d9e6a..911ce62 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,12 +1,14 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; + import reportWebVitals from "./reportWebVitals"; -import HomePage from "./pages/HomePage"; -import ChatPage from "./pages/ChatPage"; -import FriendPage from "./pages/FriendPage"; import App from "./App"; import "./index.css"; +import HomePage from "./pages/HomePage"; +import FriendPage from "./pages/FriendPage"; +import ChatListPage from "./pages/ChatListPage"; +import ChatPage from "./pages/ChatPage"; const router = createBrowserRouter([ { @@ -19,12 +21,17 @@ const router = createBrowserRouter([ }, { path: "/chats", + element: , + }, + { + path: "/chats/:receiverId", element: , }, { path: "/friends", element: , }, + ], }, ]); diff --git a/src/pages/ChatListPage.tsx b/src/pages/ChatListPage.tsx new file mode 100644 index 0000000..ad83614 --- /dev/null +++ b/src/pages/ChatListPage.tsx @@ -0,0 +1,45 @@ +import { useEffect, useState } from "react"; +import data from "../mock/user-data.json"; +import Chat from "../types/chat"; + + +export default function ChatListPage() { + const [chats, setChats] = useState([]); + + useEffect(() => { + console.log(data); + setChats(data); + + }, []); + + return ( +
+

Chat Page

+ +
+
+ {chats.map((chat) => ( +
{ window.location.href = "/chat/" + chat.username }}> +
+ + profile + +
+

{chat.name}

+

{chat.username}

+
+ +
+
+ ))} +
+
+ +
+ ); +} \ No newline at end of file diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index 8302423..ff5d02a 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -1,51 +1,58 @@ -import { useEffect, useState } from "react"; -import data from "../mock/user-data.json"; +import { useParams } from "react-router-dom"; +import { SubmitHandler, useForm } from "react-hook-form"; -interface Chat { - id: number; - name: string; - username: string; - profilePicture: string; - createdAt: string; -} +import { useMessageStore } from "../stores/messageStore"; export default function ChatPage() { - const [chats, setChats] = useState([]); - useEffect(() => { - console.log(data); - setChats(data); - - }, []); + type FormInputs = { + message: string; + } + + const { receiverId } = useParams(); + + const { messages } = useMessageStore(); + + const { register, handleSubmit, reset, formState: { errors } } = useForm({ + defaultValues: { + message: "" + } + }) + + const onSubmit: SubmitHandler = async (data) => { + console.log(data.message); + await sendMessage(data.message); + //Add to store + reset(); + } + + const sendMessage = async (content: string): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}chat/${receiverId}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + receiverId, + content, + }), + }) + console.log(response) + } return (
-

Chat Page

- -
-
- {chats.map((chat) => ( -
{ window.location.href = "/chat/" + chat.username }}> -
- - profile - -
-

{chat.name}

-

{chat.username}

-
- -
-
- ))} -
-
+

Chat Page: {receiverId}

+
{messages}
+ + +
+ + +
+ ); } \ No newline at end of file diff --git a/src/stores/messageStore.ts b/src/stores/messageStore.ts new file mode 100644 index 0000000..18a20c2 --- /dev/null +++ b/src/stores/messageStore.ts @@ -0,0 +1,11 @@ +import { create } from "zustand/react"; + +type MessageStore = { + messages: string[]; + // addMessage: (message: string) => void; +} + +export const useMessageStore = create((set) => ({ + messages: [], + // addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), +})) \ No newline at end of file diff --git a/src/types/chat.ts b/src/types/chat.ts new file mode 100644 index 0000000..928cf71 --- /dev/null +++ b/src/types/chat.ts @@ -0,0 +1,7 @@ +export default interface Chat { + id: number; + name: string; + username: string; + profilePicture: string; + createdAt: string; +} \ No newline at end of file diff --git a/src/types/message.ts b/src/types/message.ts new file mode 100644 index 0000000..417fb89 --- /dev/null +++ b/src/types/message.ts @@ -0,0 +1,3 @@ +export default interface Message { + +} \ No newline at end of file From b6b78c87ebf4a8c814149ac81bd99df3cdb84d18 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 16:30:28 +0100 Subject: [PATCH 009/118] feat: router guard for non auth user --- src/App.tsx | 18 +++++++++++++++++- src/stores/userStore.ts | 13 +++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/App.tsx b/src/App.tsx index c5f57e7..c1174dd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,6 +1,22 @@ -import { Outlet } from "react-router-dom"; +import { Outlet, useNavigate } from "react-router-dom"; import Navigation from "./components/navigation"; +import { useUserStore } from "./stores/userStore"; +import { useEffect } from "react"; + export default function App() { + const checkUserAuth = useUserStore((state) => state.checkUserAuth); + const navigate = useNavigate(); + + const checkAuth = async () => { + const isAuth = await checkUserAuth(); + if (!isAuth) { + navigate("/login"); + } + }; + useEffect(() => { + checkAuth(); + }, []); + return (
diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts index 11d60d9..a465200 100644 --- a/src/stores/userStore.ts +++ b/src/stores/userStore.ts @@ -10,6 +10,7 @@ type Action = { clearUserId: () => void; updateUsername: (newUsername: string) => void; updateUserId: (newUserId: string) => void; + checkUserAuth: () => Promise; }; export const useUserStore = create((set) => ({ username: "", @@ -18,4 +19,16 @@ export const useUserStore = create((set) => ({ clearUserId: () => set({ userId: "" }), updateUsername: (newUsername: string) => set({ username: newUsername }), updateUserId: (newUserId: string) => set({ userId: newUserId }), + checkUserAuth: async () => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/me`, + { + credentials: "include", + } + ); + if (!response.ok) { + return false; + } + return true; + }, })); From 28764f93cbade541c2a42141179d79e62eea56ac Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:36:37 +0100 Subject: [PATCH 010/118] :sparkles: get friend requests & friendRequest interface --- src/pages/FriendPage.tsx | 83 +++++++++++++++++++------------------ src/types/friend-request.ts | 7 ++++ 2 files changed, 50 insertions(+), 40 deletions(-) create mode 100644 src/types/friend-request.ts diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 1e87d16..293ba62 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -1,22 +1,47 @@ import { useEffect, useState } from "react"; -import data from "../mock/user-data.json"; - -interface Chat { - id: number; - name: string; - username: string; - profilePicture: string; - createdAt: string; -} +import FriendRequest from "../types/friend-request"; export default function FriendPage() { - const [friends, setFriends] = useState([]); + const [data, setData] = useState([]); useEffect(() => { - console.log(data); - setFriends(data); - + async function fetchData() { + const response = await fetch('http://localhost:3000/social/friend-requests', { + credentials: 'include' + }); + setData(await response.json()); + + data.forEach((d) => { + d.requestedAt = timeAgo(d.requestedAt); + }) + + console.log(data); + } + + fetchData(); }, []); + + function timeAgo(dateString: string) { + + const date = new Date(dateString); + const now = new Date(); + + const diffMs = now.getTime() - date.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHeure = Math.floor(diffMin / 60); + const diffJour = Math.floor(diffHeure / 24); + + if (diffJour > 0) { + return `${diffJour} day${diffJour > 1 ? 's' : ''} ago`; + } else if (diffHeure > 0) { + return ` ${diffHeure} hour${diffHeure > 1 ? 's' : ''} ago`; + } else if (diffMin > 0) { + return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`; + } else { + return `${diffSec} second${diffSec > 1 ? 's' : ''} ago`; + } +} return ( @@ -28,21 +53,13 @@ export default function FriendPage() { Friends Requests - {friends.map((friend) => ( -
+ {data.map((d) => ( +
-
- profile - -
-

{friend.name}

-

{friend.username}

-
+
+ send by : {d.senderId} + {timeAgo(d.requestedAt)}
@@ -50,20 +67,6 @@ export default function FriendPage() {
))} - - {friends.length} Friends - -
- {friends.map((friend) => ( -
- profile -
- ))} -
diff --git a/src/types/friend-request.ts b/src/types/friend-request.ts new file mode 100644 index 0000000..f4c3b6b --- /dev/null +++ b/src/types/friend-request.ts @@ -0,0 +1,7 @@ +interface FriendRequest { + id: number; + senderId: string; + requestedAt: string; +} + +export default FriendRequest \ No newline at end of file From 536811e64b948e65b23857762a732f1ff16150a7 Mon Sep 17 00:00:00 2001 From: Smachy Date: Tue, 5 Nov 2024 16:37:10 +0100 Subject: [PATCH 011/118] feat: load messages sent --- src/pages/ChatPage.tsx | 44 ++++++++++++++++++++++++++++++++------ src/stores/messageStore.ts | 4 ++-- src/types/message.ts | 5 ++++- 3 files changed, 43 insertions(+), 10 deletions(-) diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index ff5d02a..d16939d 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -2,6 +2,8 @@ import { useParams } from "react-router-dom"; import { SubmitHandler, useForm } from "react-hook-form"; import { useMessageStore } from "../stores/messageStore"; +import Message from "../types/message"; +import { useEffect, useState } from "react"; export default function ChatPage() { @@ -10,8 +12,9 @@ export default function ChatPage() { } const { receiverId } = useParams(); + const { messages, addMessage } = useMessageStore(); - const { messages } = useMessageStore(); + const [loadedMessages, setLoadedMessages] = useState([]); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { @@ -20,14 +23,14 @@ export default function ChatPage() { }) const onSubmit: SubmitHandler = async (data) => { - console.log(data.message); + addMessage(data.message); await sendMessage(data.message); - //Add to store reset(); } const sendMessage = async (content: string): Promise => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}chat/${receiverId}/send`, { + const messageId = crypto.randomUUID(); + await fetch(`${process.env.REACT_APP_API_BASE_URL}chat/${messageId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -38,17 +41,44 @@ export default function ChatPage() { content, }), }) - console.log(response) } + const loadMessages = async(): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}messages/${receiverId}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + const data = await response.json(); + setLoadedMessages(data); + } + + useEffect(() => { + loadMessages(); + }, [receiverId]); + return (

Chat Page: {receiverId}

-
{messages}
+ +
+ {loadedMessages.map((message, index) => ( +
{message.content}
+ ))} +
+ +

--------------------------

+ +
{messages.map((message, index) => +
{message}
+ )}
- +
diff --git a/src/stores/messageStore.ts b/src/stores/messageStore.ts index 18a20c2..45c3bef 100644 --- a/src/stores/messageStore.ts +++ b/src/stores/messageStore.ts @@ -2,10 +2,10 @@ import { create } from "zustand/react"; type MessageStore = { messages: string[]; - // addMessage: (message: string) => void; + addMessage: (message: string) => void; } export const useMessageStore = create((set) => ({ messages: [], - // addMessage: (message) => set((state) => ({ messages: [...state.messages, message] })), + addMessage: (message: string) => set((state) => ({ messages: [...state.messages, message] })), })) \ No newline at end of file diff --git a/src/types/message.ts b/src/types/message.ts index 417fb89..7eaaa35 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,3 +1,6 @@ export default interface Message { - + id: string, + emitterId: string, + content: string, + sentAt: Date, } \ No newline at end of file From 6eae37726d0373f6a7a643b87a211780dd63993c Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:38:29 +0100 Subject: [PATCH 012/118] :mute: console log deleted --- src/pages/FriendPage.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 293ba62..0fc261e 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -14,8 +14,6 @@ export default function FriendPage() { data.forEach((d) => { d.requestedAt = timeAgo(d.requestedAt); }) - - console.log(data); } fetchData(); From b160ebd8a57864cc62dbcd9a4a14f775a82a1420 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:40:38 +0100 Subject: [PATCH 013/118] :art: rename objects --- src/pages/FriendPage.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 0fc261e..020a9f9 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -2,16 +2,16 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; export default function FriendPage() { - const [data, setData] = useState([]); + const [friendRequests, setFriendRequests] = useState([]); useEffect(() => { async function fetchData() { const response = await fetch('http://localhost:3000/social/friend-requests', { credentials: 'include' }); - setData(await response.json()); + setFriendRequests(await response.json()); - data.forEach((d) => { + friendRequests.forEach((d) => { d.requestedAt = timeAgo(d.requestedAt); }) } @@ -51,13 +51,13 @@ export default function FriendPage() { Friends Requests - {data.map((d) => ( -
+ {friendRequests.map((request) => ( +
- send by : {d.senderId} - {timeAgo(d.requestedAt)} + send by : {request.senderId} + {timeAgo(request.requestedAt)}
From b6dabb03cb724457483691416edcb7fb0536a219 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:42:09 +0100 Subject: [PATCH 014/118] =?UTF-8?q?=E2=9C=92=EF=B8=8F=20write=20function?= =?UTF-8?q?=20doc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/FriendPage.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 020a9f9..cf8530f 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -19,6 +19,11 @@ export default function FriendPage() { fetchData(); }, []); + /** + * A many time ago the request was sended + * @param dateString + * @returns + */ function timeAgo(dateString: string) { const date = new Date(dateString); From f6f17f7a9c5e5969d7547cde81856a26654ec469 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:51:57 +0100 Subject: [PATCH 015/118] :art: move the timeAgo function to the utils folder --- src/pages/FriendPage.tsx | 33 +++------------------------------ src/utils/dateFormater.tsx | 27 +++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 30 deletions(-) create mode 100644 src/utils/dateFormater.tsx diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index cf8530f..4bc36f6 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; +import { dateFormater } from "../utils/dateFormater"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); @@ -12,41 +13,13 @@ export default function FriendPage() { setFriendRequests(await response.json()); friendRequests.forEach((d) => { - d.requestedAt = timeAgo(d.requestedAt); + d.requestedAt = dateFormater(d.requestedAt); }) } fetchData(); }, []); - /** - * A many time ago the request was sended - * @param dateString - * @returns - */ - function timeAgo(dateString: string) { - - const date = new Date(dateString); - const now = new Date(); - - const diffMs = now.getTime() - date.getTime(); - const diffSec = Math.floor(diffMs / 1000); - const diffMin = Math.floor(diffSec / 60); - const diffHeure = Math.floor(diffMin / 60); - const diffJour = Math.floor(diffHeure / 24); - - if (diffJour > 0) { - return `${diffJour} day${diffJour > 1 ? 's' : ''} ago`; - } else if (diffHeure > 0) { - return ` ${diffHeure} hour${diffHeure > 1 ? 's' : ''} ago`; - } else if (diffMin > 0) { - return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`; - } else { - return `${diffSec} second${diffSec > 1 ? 's' : ''} ago`; - } -} - - return (

Friend Page

@@ -62,7 +35,7 @@ export default function FriendPage() {
send by : {request.senderId} - {timeAgo(request.requestedAt)} + {dateFormater(request.requestedAt)}
diff --git a/src/utils/dateFormater.tsx b/src/utils/dateFormater.tsx new file mode 100644 index 0000000..c191245 --- /dev/null +++ b/src/utils/dateFormater.tsx @@ -0,0 +1,27 @@ +/** + * A many time ago the request was sended + * @param dateString + * @returns +*/ + +export function dateFormater(dateString: string) { + + const date = new Date(dateString); + const now = new Date(); + + const diffMs = now.getTime() - date.getTime(); + const diffSec = Math.floor(diffMs / 1000); + const diffMin = Math.floor(diffSec / 60); + const diffHeure = Math.floor(diffMin / 60); + const diffJour = Math.floor(diffHeure / 24); + + if (diffJour > 0) { + return `${diffJour} day${diffJour > 1 ? 's' : ''} ago`; + } else if (diffHeure > 0) { + return ` ${diffHeure} hour${diffHeure > 1 ? 's' : ''} ago`; + } else if (diffMin > 0) { + return `${diffMin} minute${diffMin > 1 ? 's' : ''} ago`; + } else { + return `${diffSec} second${diffSec > 1 ? 's' : ''} ago`; + } +} \ No newline at end of file From bc80ef587a2f64f80e891a7c1d2c7a6ae6a9cf90 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 16:58:00 +0100 Subject: [PATCH 016/118] :art: friend request card in a component --- src/components/friend-request-card.tsx | 19 +++++++++++++++++++ src/pages/FriendPage.tsx | 14 ++------------ 2 files changed, 21 insertions(+), 12 deletions(-) create mode 100644 src/components/friend-request-card.tsx diff --git a/src/components/friend-request-card.tsx b/src/components/friend-request-card.tsx new file mode 100644 index 0000000..57bfcbc --- /dev/null +++ b/src/components/friend-request-card.tsx @@ -0,0 +1,19 @@ +import FriendRequest from "../types/friend-request"; +import { dateFormater } from "../utils/dateFormater"; + +export default function FriendRequestCard(request: FriendRequest) { + return ( +
+
+ +
+ send by : {request.senderId} + {dateFormater(request.requestedAt)} +
+ + + +
+
+ ); +} \ No newline at end of file diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 4bc36f6..f21fe8a 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; +import FriendRequestCard from "../components/friend-request-card"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); @@ -30,18 +31,7 @@ export default function FriendPage() { Friends Requests {friendRequests.map((request) => ( -
-
- -
- send by : {request.senderId} - {dateFormater(request.requestedAt)} -
- - - -
-
+ ))}
From c0b0697c0bb814cab1ecaa84c4449822bb0cb7ec Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 16:59:05 +0100 Subject: [PATCH 017/118] chore : refactor user store --- src/stores/userStore.ts | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts index a465200..1ffe18b 100644 --- a/src/stores/userStore.ts +++ b/src/stores/userStore.ts @@ -6,19 +6,15 @@ type State = { }; type Action = { - clearUsername: () => void; - clearUserId: () => void; - updateUsername: (newUsername: string) => void; - updateUserId: (newUserId: string) => void; + clearUser: () => void; + updateUser: (newUser: State) => void; checkUserAuth: () => Promise; }; export const useUserStore = create((set) => ({ username: "", userId: "", - clearUsername: () => set({ username: "" }), - clearUserId: () => set({ userId: "" }), - updateUsername: (newUsername: string) => set({ username: newUsername }), - updateUserId: (newUserId: string) => set({ userId: newUserId }), + clearUser: () => set({ username: "", userId: "" }), + updateUser: (newUser) => set(newUser), checkUserAuth: async () => { const response = await fetch( `${process.env.REACT_APP_API_BASE_URL}/auth/me`, From 4dea249c86970481fcfa8be4177d678d5208de2b Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 17:09:00 +0100 Subject: [PATCH 018/118] :art: move the api call to a service --- src/pages/FriendPage.tsx | 11 +++++++---- src/service/friend-request.service.ts | 13 +++++++++++++ 2 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 src/service/friend-request.service.ts diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index f21fe8a..733b3d2 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -2,16 +2,19 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; import FriendRequestCard from "../components/friend-request-card"; +import { fetchFriendRequests } from "../service/friend-request.service"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); useEffect(() => { async function fetchData() { - const response = await fetch('http://localhost:3000/social/friend-requests', { - credentials: 'include' - }); - setFriendRequests(await response.json()); + async function loadFriendRequests() { + const requests = await fetchFriendRequests(); + setFriendRequests(requests); + } + + loadFriendRequests(); friendRequests.forEach((d) => { d.requestedAt = dateFormater(d.requestedAt); diff --git a/src/service/friend-request.service.ts b/src/service/friend-request.service.ts new file mode 100644 index 0000000..d5598d8 --- /dev/null +++ b/src/service/friend-request.service.ts @@ -0,0 +1,13 @@ +import FriendRequest from "../types/friend-request"; + +export async function fetchFriendRequests() { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-requests`, { + credentials: 'include' + }); + const friendRequests = await response.json(); + + // Formatez la date pour chaque demande d'ami + return friendRequests.map((request: FriendRequest) => ({ + ...request, + })); +} \ No newline at end of file From f4cf5d824f92a91c1cc654397299402fa5056dec Mon Sep 17 00:00:00 2001 From: Smachy Date: Tue, 5 Nov 2024 18:24:31 +0100 Subject: [PATCH 019/118] feat: store messages --- src/pages/ChatPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx index d16939d..7f224b1 100644 --- a/src/pages/ChatPage.tsx +++ b/src/pages/ChatPage.tsx @@ -30,7 +30,7 @@ export default function ChatPage() { const sendMessage = async (content: string): Promise => { const messageId = crypto.randomUUID(); - await fetch(`${process.env.REACT_APP_API_BASE_URL}chat/${messageId}/send`, { + await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -44,7 +44,7 @@ export default function ChatPage() { } const loadMessages = async(): Promise => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}messages/${receiverId}/`, { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { method: 'GET', headers: { 'Content-Type': 'application/json', From 373a63aad7be780e371eb552beb66f6f5e72c2b7 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 18:48:46 +0100 Subject: [PATCH 020/118] :sparkles: loader component created --- src/components/loader/loader.css | 17 +++++++++++++++++ src/components/loader/loader.tsx | 9 +++++++++ 2 files changed, 26 insertions(+) create mode 100644 src/components/loader/loader.css create mode 100644 src/components/loader/loader.tsx diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css new file mode 100644 index 0000000..25dae70 --- /dev/null +++ b/src/components/loader/loader.css @@ -0,0 +1,17 @@ +/* HTML:
*/ +.loader { + width: 50px; + padding: 8px; + aspect-ratio: 1; + border-radius: 50%; + background: #25b09b; + --_m: + conic-gradient(#0000 10%,#000), + linear-gradient(#000 0 0) content-box; + -webkit-mask: var(--_m); + mask: var(--_m); + -webkit-mask-composite: source-out; + mask-composite: subtract; + animation: l3 1s infinite linear; +} +@keyframes l3 {to{transform: rotate(1turn)}} \ No newline at end of file diff --git a/src/components/loader/loader.tsx b/src/components/loader/loader.tsx new file mode 100644 index 0000000..60c5837 --- /dev/null +++ b/src/components/loader/loader.tsx @@ -0,0 +1,9 @@ +import './loader.css'; + +export default function Loader() { + return ( +
+
+
+ ); +} \ No newline at end of file From b3d299911760cba678d6dc9b7382a8eb9168a41b Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Tue, 5 Nov 2024 18:49:06 +0100 Subject: [PATCH 021/118] :sparkles: loader condition added on friendrequestPage --- src/pages/FriendPage.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 733b3d2..3876d91 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -3,15 +3,18 @@ import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; import FriendRequestCard from "../components/friend-request-card"; import { fetchFriendRequests } from "../service/friend-request.service"; +import Loader from "../components/loader/loader"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); + const [loading, setLoading] = useState(true); useEffect(() => { async function fetchData() { async function loadFriendRequests() { const requests = await fetchFriendRequests(); setFriendRequests(requests); + setLoading(false); } loadFriendRequests(); @@ -23,9 +26,11 @@ export default function FriendPage() { fetchData(); }, []); - + return (
+ {loading ? : null} +

Friend Page

From de53d03b02935a9e9910f326c5bd62a38d43a071 Mon Sep 17 00:00:00 2001 From: Smachy24 Date: Tue, 5 Nov 2024 20:25:54 +0100 Subject: [PATCH 022/118] feat: error on get messages if bad id or if users not friends --- src/components/loaders/messages.loader.tsx | 19 ++++ src/index.tsx | 4 +- src/pages/ChatPage.tsx | 88 ------------------- .../{ChatListPage.tsx => chat-list.page.tsx} | 0 src/pages/chat.page.tsx | 81 +++++++++++++++++ src/services/message.service.ts | 31 +++++++ .../{messageStore.ts => message.store.ts} | 0 7 files changed, 133 insertions(+), 90 deletions(-) create mode 100644 src/components/loaders/messages.loader.tsx delete mode 100644 src/pages/ChatPage.tsx rename src/pages/{ChatListPage.tsx => chat-list.page.tsx} (100%) create mode 100644 src/pages/chat.page.tsx create mode 100644 src/services/message.service.ts rename src/stores/{messageStore.ts => message.store.ts} (100%) diff --git a/src/components/loaders/messages.loader.tsx b/src/components/loaders/messages.loader.tsx new file mode 100644 index 0000000..c7fa803 --- /dev/null +++ b/src/components/loaders/messages.loader.tsx @@ -0,0 +1,19 @@ +import { ReactNode, useEffect } from "react"; +import { useNavigate } from "react-router-dom"; + +type LoaderProps = { + receiverId?: string; + children: ReactNode; +}; + +export default function MessagesLoader({ receiverId, children }: LoaderProps) { + const navigate = useNavigate(); + + useEffect(() => { + if (!receiverId) { + navigate('/chats'); + } + }, [receiverId, navigate]); + + return <>{children}; +} diff --git a/src/index.tsx b/src/index.tsx index 911ce62..06f4c73 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -7,8 +7,8 @@ import App from "./App"; import "./index.css"; import HomePage from "./pages/HomePage"; import FriendPage from "./pages/FriendPage"; -import ChatListPage from "./pages/ChatListPage"; -import ChatPage from "./pages/ChatPage"; +import ChatListPage from "./pages/chat-list.page"; +import ChatPage from "./pages/chat.page"; const router = createBrowserRouter([ { diff --git a/src/pages/ChatPage.tsx b/src/pages/ChatPage.tsx deleted file mode 100644 index 7f224b1..0000000 --- a/src/pages/ChatPage.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import { useParams } from "react-router-dom"; -import { SubmitHandler, useForm } from "react-hook-form"; - -import { useMessageStore } from "../stores/messageStore"; -import Message from "../types/message"; -import { useEffect, useState } from "react"; - -export default function ChatPage() { - - type FormInputs = { - message: string; - } - - const { receiverId } = useParams(); - const { messages, addMessage } = useMessageStore(); - - const [loadedMessages, setLoadedMessages] = useState([]); - - const { register, handleSubmit, reset, formState: { errors } } = useForm({ - defaultValues: { - message: "" - } - }) - - const onSubmit: SubmitHandler = async (data) => { - addMessage(data.message); - await sendMessage(data.message); - reset(); - } - - const sendMessage = async (content: string): Promise => { - const messageId = crypto.randomUUID(); - await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - receiverId, - content, - }), - }) - } - - const loadMessages = async(): Promise => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - }) - const data = await response.json(); - setLoadedMessages(data); - } - - useEffect(() => { - loadMessages(); - }, [receiverId]); - - return ( -
-

Chat Page: {receiverId}

- -
- {loadedMessages.map((message, index) => ( -
{message.content}
- ))} -
- -

--------------------------

- -
{messages.map((message, index) => -
{message}
- )}
- - -
- - -
- -
- - ); -} \ No newline at end of file diff --git a/src/pages/ChatListPage.tsx b/src/pages/chat-list.page.tsx similarity index 100% rename from src/pages/ChatListPage.tsx rename to src/pages/chat-list.page.tsx diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx new file mode 100644 index 0000000..af6c88d --- /dev/null +++ b/src/pages/chat.page.tsx @@ -0,0 +1,81 @@ +import {useNavigate, useParams} from "react-router-dom"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { useEffect, useState } from "react"; + +import { useMessageStore } from "../stores/message.store"; +import Message from "../types/message"; +import {sendMessage, fetchMessages} from "../services/message.service"; +import MessagesLoader from "../components/loaders/messages.loader"; + +export default function ChatPage() { + + type FormInputs = { + message: string; + } + + const navigate = useNavigate(); + + const { receiverId } = useParams(); + + const { messages, addMessage } = useMessageStore(); + + const [loadedMessages, setLoadedMessages] = useState([]); + + const { register, handleSubmit, reset, formState: { errors } } = useForm({ + defaultValues: { + message: "" + } + }) + + const onSubmit: SubmitHandler = async (data) => { + if (!receiverId) return; + addMessage(data.message); + await sendMessage(receiverId, data.message); + reset(); + } + + + + useEffect(() => { + if (!receiverId) return; + const loadMessages = async (): Promise => { + try { + const messages = await fetchMessages(receiverId); + setLoadedMessages(messages); + } catch (error) { + navigate('/chats'); + } + }; + loadMessages(); + }, [receiverId]); + + + return ( + + +
+

Chat Page: {receiverId}

+ +
+ {loadedMessages.map((message, index) => ( +
{message.content}
+ ))} +
+ +

--------------------------

+ +
{messages.map((message, index) => +
{message}
+ )}
+ + +
+ + +
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/services/message.service.ts b/src/services/message.service.ts new file mode 100644 index 0000000..b6759fb --- /dev/null +++ b/src/services/message.service.ts @@ -0,0 +1,31 @@ +import Message from "../types/message"; +import { StatusCodes } from "http-status-codes"; + +export const sendMessage = async (receiverId: string, content: string): Promise => { + const messageId = crypto.randomUUID(); + await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + receiverId, + content, + }), + }) +} + +export const fetchMessages = async(receiverId: string): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + if(!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + return await response.json(); +} \ No newline at end of file diff --git a/src/stores/messageStore.ts b/src/stores/message.store.ts similarity index 100% rename from src/stores/messageStore.ts rename to src/stores/message.store.ts From c190003ed684e852858081e4fc82906c8194c93d Mon Sep 17 00:00:00 2001 From: Smachy24 Date: Tue, 5 Nov 2024 20:53:04 +0100 Subject: [PATCH 023/118] feat: event new messages --- src/pages/chat.page.tsx | 16 ++++++--- src/services/message.service.ts | 63 ++++++++++++++++++++------------- 2 files changed, 50 insertions(+), 29 deletions(-) diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index af6c88d..f64fbb0 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -4,7 +4,7 @@ import { useEffect, useState } from "react"; import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; -import {sendMessage, fetchMessages} from "../services/message.service"; +import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; export default function ChatPage() { @@ -34,8 +34,6 @@ export default function ChatPage() { reset(); } - - useEffect(() => { if (!receiverId) return; const loadMessages = async (): Promise => { @@ -47,8 +45,18 @@ export default function ChatPage() { } }; loadMessages(); - }, [receiverId]); + const handleNewMessage = (data: Message) => { + console.log("Nouveau message reçu :", data); + setLoadedMessages((prevMessages) => [...prevMessages, data]); + }; + + const eventSource = eventFetchMessages(handleNewMessage); + + return () => { + eventSource.close(); + }; + }, [receiverId]); return ( diff --git a/src/services/message.service.ts b/src/services/message.service.ts index b6759fb..da5f7b3 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -1,31 +1,44 @@ import Message from "../types/message"; -import { StatusCodes } from "http-status-codes"; export const sendMessage = async (receiverId: string, content: string): Promise => { - const messageId = crypto.randomUUID(); - await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - receiverId, - content, - }), - }) + const messageId = crypto.randomUUID(); + await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + receiverId, + content, + }), + }) } -export const fetchMessages = async(receiverId: string): Promise => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - }) - if(!response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); - } - return await response.json(); +export const fetchMessages = async (receiverId: string): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + //TODO : Attention pas d'erreur si utilisateur n'existe pas ou pas ami + return await response.json() +} + +export const eventFetchMessages = (onMessageReceived: (data: Message) => void) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener('message-received', (event) => { + const data = JSON.parse(event.data); + onMessageReceived(data); + }) + + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; } \ No newline at end of file From 8a6de307c1b7f34cabd7e7eef41bcdf47380cc57 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:06:24 +0100 Subject: [PATCH 024/118] feat: Router guards --- .env.example | 3 ++- package.json | 4 +++- src/components/GuestRoute.tsx | 18 ++++++++++++++++++ src/components/ProtectedRoute.tsx | 18 ++++++++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 src/components/GuestRoute.tsx create mode 100644 src/components/ProtectedRoute.tsx diff --git a/.env.example b/.env.example index 9a69c24..901336d 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -PORT=5000 \ No newline at end of file +PORT=5000 +REACT_APP_API_BASE_URL=http://localhost:3000 \ No newline at end of file diff --git a/package.json b/package.json index b9c508a..e99c9ce 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,12 @@ "@types/react-dom": "^18.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.1" }, "scripts": { "start": "react-scripts start", diff --git a/src/components/GuestRoute.tsx b/src/components/GuestRoute.tsx new file mode 100644 index 0000000..9e0b19d --- /dev/null +++ b/src/components/GuestRoute.tsx @@ -0,0 +1,18 @@ +import { Outlet, useNavigate } from "react-router-dom"; +import { useUserStore } from "../stores/userStore"; +import { useEffect } from "react"; + +export default function GuestRoute() { + const checkUserAuth = useUserStore((state) => state.checkUserAuth); + const navigate = useNavigate(); + const checkAuth = async () => { + const isAuth = await checkUserAuth(); + if (isAuth) { + navigate("/"); + } + }; + useEffect(() => { + checkAuth(); + }, []); + return ; +} diff --git a/src/components/ProtectedRoute.tsx b/src/components/ProtectedRoute.tsx new file mode 100644 index 0000000..f1f1a4f --- /dev/null +++ b/src/components/ProtectedRoute.tsx @@ -0,0 +1,18 @@ +import { Outlet, useNavigate } from "react-router-dom"; +import { useUserStore } from "../stores/userStore"; +import { useEffect } from "react"; + +export default function ProtectedRoute() { + const checkUserAuth = useUserStore((state) => state.checkUserAuth); + const navigate = useNavigate(); + const checkAuth = async () => { + const isAuth = await checkUserAuth(); + if (!isAuth) { + navigate("/login"); + } + }; + useEffect(() => { + checkAuth(); + }, []); + return ; +} From 9b5f1b72e9c20fdbfb9e5072d9159e741861ee6e Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:08:05 +0100 Subject: [PATCH 025/118] refactor : Less repetitive code form register and login form --- src/components/Form/AuthForm.tsx | 42 ++++++++++++++++ src/pages/LoginPage.tsx | 82 ++------------------------------ src/pages/RegisterPage.tsx | 58 ++-------------------- 3 files changed, 49 insertions(+), 133 deletions(-) create mode 100644 src/components/Form/AuthForm.tsx diff --git a/src/components/Form/AuthForm.tsx b/src/components/Form/AuthForm.tsx new file mode 100644 index 0000000..409ceac --- /dev/null +++ b/src/components/Form/AuthForm.tsx @@ -0,0 +1,42 @@ +import { useState } from "react"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { UserDTO } from "../../types/userDto"; + +interface AuthFormProps { + submitFn: (data: UserDTO) => Promise; +} + +export default function AuthForm({ submitFn }: AuthFormProps) { + const { + register, + handleSubmit, + formState: { errors }, + } = useForm(); + + const [resError, setResError] = useState(); + + const onSubmit: SubmitHandler = async (data) => { + try { + await submitFn(data); + } catch (error: any) { + setResError(error.message); + } + }; + + return ( +
+ + + {resError && {resError}} + +
+ ); +} diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 2a3fd94..0546560 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,89 +1,15 @@ -import { useState } from "react"; -import { useForm, SubmitHandler } from "react-hook-form"; import { useNavigate } from "react-router-dom"; -import { useUserStore } from "../stores/userStore"; - -type Inputs = { - username: string; - password: string; -}; +import { loginUser } from "../services/auth.service"; +import AuthForm from "../components/Form/AuthForm"; export default function LoginPage() { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm(); - - const [resError, setResError] = useState(); const navigate = useNavigate(); - - const onSubmit: SubmitHandler = async (data) => { - const response = await fetch( - `${process.env.REACT_APP_API_BASE_URL}/auth/login`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - } - ); - if (!response.ok) { - const { message } = await response.json(); - setResError(message); - return; - } - - setUserStore(); - }; - - const updateUserId = useUserStore((state) => state.updateUserId); - const updateUsername = useUserStore((state) => state.updateUsername); - - const setUserStore = async () => { - const response = await fetch( - `${process.env.REACT_APP_API_BASE_URL}/auth/me`, - { - credentials: "include", - } - ); - - if (!response.ok) { - const { message } = await response.json(); - setResError(message); - return; - } - - const user = await response.json(); - updateUserId(user.id); - updateUsername(user.username); - }; - return ( <>

Connexion

- -
- - - {resError && {resError}} - -
- + ); diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx index fca1b90..274813e 100644 --- a/src/pages/RegisterPage.tsx +++ b/src/pages/RegisterPage.tsx @@ -1,65 +1,13 @@ -import { useState } from "react"; -import { useForm, SubmitHandler } from "react-hook-form"; import { useNavigate } from "react-router-dom"; - -type Inputs = { - username: string; - password: string; -}; +import { registerUser } from "../services/auth.service"; +import AuthForm from "../components/Form/AuthForm"; export default function RegisterPage() { - const { - register, - handleSubmit, - formState: { errors }, - } = useForm(); - - const [resError, setResError] = useState(); - const navigate = useNavigate(); - - const onSubmit: SubmitHandler = async (data) => { - const response = await fetch( - `${process.env.REACT_APP_API_BASE_URL}/auth/register`, - { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - } - ); - if (!response.ok) { - const { message } = await response.json(); - setResError(message); - return; - } - - navigate("/login"); - }; - return ( <>

Inscription

- -
- - - {resError && {resError}} - -
- + ); From f83f571b724e420defc31620740a71c0159377b5 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:08:50 +0100 Subject: [PATCH 026/118] feat : add auth service --- src/services/auth.service.ts | 37 ++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 src/services/auth.service.ts diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts new file mode 100644 index 0000000..f08d8a2 --- /dev/null +++ b/src/services/auth.service.ts @@ -0,0 +1,37 @@ +import { UserDTO } from "../types/userDto"; + +export async function loginUser(data: UserDTO) { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/login`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + } + ); + if (!response.ok) { + const { message } = await response.json(); + throw new Error(message); + } +} + +export async function registerUser(data: UserDTO) { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/register`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + credentials: "include", + body: JSON.stringify(data), + } + ); + if (!response.ok) { + const { message } = await response.json(); + throw new Error(message); + } +} From 1abcb8884b0f88bd9bca49054fadce7bd6d117c2 Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:09:24 +0100 Subject: [PATCH 027/118] feat : add userDto --- src/App.tsx | 17 +---------------- src/index.tsx | 40 ++++++++++++++++++++++++++-------------- src/types/userDto.ts | 4 ++++ 3 files changed, 31 insertions(+), 30 deletions(-) create mode 100644 src/types/userDto.ts diff --git a/src/App.tsx b/src/App.tsx index c1174dd..b4af07e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,22 +1,7 @@ -import { Outlet, useNavigate } from "react-router-dom"; +import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; -import { useUserStore } from "./stores/userStore"; -import { useEffect } from "react"; export default function App() { - const checkUserAuth = useUserStore((state) => state.checkUserAuth); - const navigate = useNavigate(); - - const checkAuth = async () => { - const isAuth = await checkUserAuth(); - if (!isAuth) { - navigate("/login"); - } - }; - useEffect(() => { - checkAuth(); - }, []); - return (
diff --git a/src/index.tsx b/src/index.tsx index ca9c982..be87b0b 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,6 +9,8 @@ import App from "./App"; import "./index.css"; import RegisterPage from "./pages/RegisterPage"; import LoginPage from "./pages/LoginPage"; +import ProtectedRoute from "./components/ProtectedRoute"; +import GuestRoute from "./components/GuestRoute"; const router = createBrowserRouter([ { @@ -16,27 +18,37 @@ const router = createBrowserRouter([ element: , children: [ { - index: true, - element: , + element: , + children: [ + { + index: true, + element: , + }, + { + path: "/chats", + element: , + }, + { + path: "/friends", + element: , + }, + ], }, + ], + }, + { + element: , + children: [ { - path: "/chats", - element: , + path: "/register", + element: , }, { - path: "/friends", - element: , + path: "/login", + element: , }, ], }, - { - path: "/register", - element: , - }, - { - path: "/login", - element: , - }, ]); const root = ReactDOM.createRoot( diff --git a/src/types/userDto.ts b/src/types/userDto.ts new file mode 100644 index 0000000..9999bca --- /dev/null +++ b/src/types/userDto.ts @@ -0,0 +1,4 @@ +export interface UserDTO { + username: string; + password: string; +} From 309b2365459a3868e5395f8483cddf655f403eae Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:21:49 +0100 Subject: [PATCH 028/118] feat : redirect user to home page after login or register --- src/pages/LoginPage.tsx | 8 +++++++- src/pages/RegisterPage.tsx | 8 +++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/pages/LoginPage.tsx b/src/pages/LoginPage.tsx index 0546560..ed8cb1d 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/LoginPage.tsx @@ -1,13 +1,19 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; import AuthForm from "../components/Form/AuthForm"; +import { UserDTO } from "../types/userDto"; export default function LoginPage() { const navigate = useNavigate(); + + const onSubmit = async (data: UserDTO) => { + await loginUser(data); + navigate("/"); + }; return ( <>

Connexion

- + diff --git a/src/pages/RegisterPage.tsx b/src/pages/RegisterPage.tsx index 274813e..177d2f1 100644 --- a/src/pages/RegisterPage.tsx +++ b/src/pages/RegisterPage.tsx @@ -1,13 +1,19 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; import AuthForm from "../components/Form/AuthForm"; +import { UserDTO } from "../types/userDto"; export default function RegisterPage() { const navigate = useNavigate(); + + const onSubmit = async (data: UserDTO) => { + await registerUser(data); + navigate("/"); + }; return ( <>

Inscription

- + ); From 0d976267ca4172df3202fca21297f4a4e7c22f1d Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 21:26:44 +0100 Subject: [PATCH 029/118] feat : add logout button --- src/components/navigation.tsx | 6 +++++- src/services/auth.service.ts | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 1704522..20f15c0 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -1,5 +1,6 @@ import * as React from "react"; import { Link } from "react-router-dom"; +import { logoutUser } from "../services/auth.service"; export default function Navigation() { return ( @@ -7,6 +8,9 @@ export default function Navigation() { Chats Friends Settings + + Logout +
); -} \ No newline at end of file +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index f08d8a2..972734e 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -35,3 +35,17 @@ export async function registerUser(data: UserDTO) { throw new Error(message); } } + +export async function logoutUser() { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/logout`, + { + method: "POST", + credentials: "include", + } + ); + if (!response.ok) { + const { message } = await response.json(); + throw new Error(message); + } +} From e64c797f9cfbcac989737aa10eb01da6abe94e5b Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 22:13:28 +0100 Subject: [PATCH 030/118] feat : add current user friend list --- src/pages/HomePage.tsx | 27 ++++++++++++++++++++++++++- src/services/friends.service.ts | 18 ++++++++++++++++++ src/types/friends.ts | 5 +++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/services/friends.service.ts create mode 100644 src/types/friends.ts diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx index 3081f29..e6ee440 100644 --- a/src/pages/HomePage.tsx +++ b/src/pages/HomePage.tsx @@ -1,7 +1,32 @@ +import { useEffect, useState } from "react"; +import { Friend } from "../types/friends"; +import { getUserFriends } from "../services/friends.service"; + export default function HomePage() { + const [friends, setFriends] = useState([]); + + const fetchFriends = async () => { + const friendList = await getUserFriends(); + setFriends(friendList); + }; + + const sortedByDateFriendsList = friends.sort((a, b) => { + return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(); + }); + useEffect(() => { + fetchFriends(); + }, []); + return (

Home Page

+
+
    + {sortedByDateFriendsList.map((friend) => ( +
  • {friend.username}
  • + ))} +
+
); -} \ No newline at end of file +} diff --git a/src/services/friends.service.ts b/src/services/friends.service.ts new file mode 100644 index 0000000..470fc85 --- /dev/null +++ b/src/services/friends.service.ts @@ -0,0 +1,18 @@ +import { Friend } from "../types/friends"; + +export const getUserFriends = async (): Promise => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/social/friends`, + { + method: "GET", + credentials: "include", + } + ); + if (!response.ok) { + const { message } = await response.json(); + console.error(message); + return []; + } + + return (await response.json()) as Friend[]; +}; diff --git a/src/types/friends.ts b/src/types/friends.ts new file mode 100644 index 0000000..4d5256c --- /dev/null +++ b/src/types/friends.ts @@ -0,0 +1,5 @@ +export interface Friend { + userId: string; + username: string; + startedAt: string; +} From f41df9781c7e7eed89f997266fca4df06c3354ab Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:13:25 +0100 Subject: [PATCH 031/118] :fire: better useEffect fn --- src/pages/FriendPage.tsx | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index 3876d91..c65b4db 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -10,21 +10,17 @@ export default function FriendPage() { const [loading, setLoading] = useState(true); useEffect(() => { - async function fetchData() { - async function loadFriendRequests() { - const requests = await fetchFriendRequests(); - setFriendRequests(requests); - setLoading(false); - } - - loadFriendRequests(); - - friendRequests.forEach((d) => { - d.requestedAt = dateFormater(d.requestedAt); - }) + async function loadFriendRequests() { + const requests = await fetchFriendRequests(); + setFriendRequests(requests); + setLoading(false); } - - fetchData(); + + loadFriendRequests(); + + friendRequests.forEach((d) => { + d.requestedAt = dateFormater(d.requestedAt); + }) }, []); return ( From a261e8dd8e0e139d80ba04f493b47425bd331379 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:16:30 +0100 Subject: [PATCH 032/118] :fire: remove comments --- src/service/friend-request.service.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/service/friend-request.service.ts b/src/service/friend-request.service.ts index d5598d8..0d95b87 100644 --- a/src/service/friend-request.service.ts +++ b/src/service/friend-request.service.ts @@ -5,8 +5,7 @@ export async function fetchFriendRequests() { credentials: 'include' }); const friendRequests = await response.json(); - - // Formatez la date pour chaque demande d'ami + return friendRequests.map((request: FriendRequest) => ({ ...request, })); From a0b414a5436b2ab53f43016cbc279c75291057ed Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:17:35 +0100 Subject: [PATCH 033/118] :art: new services folder name --- src/pages/FriendPage.tsx | 2 +- src/{service => services}/friend-request.service.ts | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename src/{service => services}/friend-request.service.ts (100%) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index c65b4db..a6ef723 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; import FriendRequestCard from "../components/friend-request-card"; -import { fetchFriendRequests } from "../service/friend-request.service"; +import { fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loader/loader"; export default function FriendPage() { diff --git a/src/service/friend-request.service.ts b/src/services/friend-request.service.ts similarity index 100% rename from src/service/friend-request.service.ts rename to src/services/friend-request.service.ts From 7c0d82ef2bc407d3c7da2e7f8b83d550d3e6794b Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:21:16 +0100 Subject: [PATCH 034/118] :fire: remove comments --- src/components/loader/loader.css | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/loader/loader.css b/src/components/loader/loader.css index 25dae70..3cb8dd0 100644 --- a/src/components/loader/loader.css +++ b/src/components/loader/loader.css @@ -1,4 +1,3 @@ -/* HTML:
*/ .loader { width: 50px; padding: 8px; From 2f433e4fefe8ab412dae7794331063c859bd5d93 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:22:29 +0100 Subject: [PATCH 035/118] :art: move loadFriendRequest --- src/pages/FriendPage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pages/FriendPage.tsx b/src/pages/FriendPage.tsx index a6ef723..e047204 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/FriendPage.tsx @@ -10,12 +10,6 @@ export default function FriendPage() { const [loading, setLoading] = useState(true); useEffect(() => { - async function loadFriendRequests() { - const requests = await fetchFriendRequests(); - setFriendRequests(requests); - setLoading(false); - } - loadFriendRequests(); friendRequests.forEach((d) => { @@ -23,6 +17,12 @@ export default function FriendPage() { }) }, []); + async function loadFriendRequests() { + const requests = await fetchFriendRequests(); + setFriendRequests(requests); + setLoading(false); + } + return (
{loading ? : null} From f9642e699cbd303d5e461f5995fbb405f515d893 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:24:28 +0100 Subject: [PATCH 036/118] rename component and page files --- src/components/loader/{loader.tsx => loader.component.tsx} | 0 src/index.tsx | 2 +- src/pages/{FriendPage.tsx => friend-page.page.tsx} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/components/loader/{loader.tsx => loader.component.tsx} (100%) rename src/pages/{FriendPage.tsx => friend-page.page.tsx} (95%) diff --git a/src/components/loader/loader.tsx b/src/components/loader/loader.component.tsx similarity index 100% rename from src/components/loader/loader.tsx rename to src/components/loader/loader.component.tsx diff --git a/src/index.tsx b/src/index.tsx index 69d9e6a..da71b43 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import reportWebVitals from "./reportWebVitals"; import HomePage from "./pages/HomePage"; import ChatPage from "./pages/ChatPage"; -import FriendPage from "./pages/FriendPage"; +import FriendPage from "./pages/friend-page.page"; import App from "./App"; import "./index.css"; diff --git a/src/pages/FriendPage.tsx b/src/pages/friend-page.page.tsx similarity index 95% rename from src/pages/FriendPage.tsx rename to src/pages/friend-page.page.tsx index e047204..97ad389 100644 --- a/src/pages/FriendPage.tsx +++ b/src/pages/friend-page.page.tsx @@ -3,7 +3,7 @@ import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; import FriendRequestCard from "../components/friend-request-card"; import { fetchFriendRequests } from "../services/friend-request.service"; -import Loader from "../components/loader/loader"; +import Loader from "../components/loader/loader.component"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); From 815137b7471439307fafa8ba8d3ad13e92b3b02c Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:26:47 +0100 Subject: [PATCH 037/118] rename some files --- ...riend-request-card.tsx => friend-request-card.component.tsx} | 0 src/index.tsx | 2 +- src/pages/{friend-page.page.tsx => friend.page.tsx} | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/components/{friend-request-card.tsx => friend-request-card.component.tsx} (100%) rename src/pages/{friend-page.page.tsx => friend.page.tsx} (98%) diff --git a/src/components/friend-request-card.tsx b/src/components/friend-request-card.component.tsx similarity index 100% rename from src/components/friend-request-card.tsx rename to src/components/friend-request-card.component.tsx diff --git a/src/index.tsx b/src/index.tsx index da71b43..e923392 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,7 +4,7 @@ import { createBrowserRouter, RouterProvider } from "react-router-dom"; import reportWebVitals from "./reportWebVitals"; import HomePage from "./pages/HomePage"; import ChatPage from "./pages/ChatPage"; -import FriendPage from "./pages/friend-page.page"; +import FriendPage from "./pages/friend.page"; import App from "./App"; import "./index.css"; diff --git a/src/pages/friend-page.page.tsx b/src/pages/friend.page.tsx similarity index 98% rename from src/pages/friend-page.page.tsx rename to src/pages/friend.page.tsx index 97ad389..7dbf2f2 100644 --- a/src/pages/friend-page.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; -import FriendRequestCard from "../components/friend-request-card"; +import FriendRequestCard from "../components/friend-request-card.component"; import { fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loader/loader.component"; From 612d2554ac6df5f31e0901275fc02ccb452f811d Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:28:32 +0100 Subject: [PATCH 038/118] :fire: remove loop --- src/pages/friend.page.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index 7dbf2f2..e247f61 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -11,10 +11,6 @@ export default function FriendPage() { useEffect(() => { loadFriendRequests(); - - friendRequests.forEach((d) => { - d.requestedAt = dateFormater(d.requestedAt); - }) }, []); async function loadFriendRequests() { From 808549567747f42759d8fa1642ac04def2a20fef Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:29:40 +0100 Subject: [PATCH 039/118] :fire: remoive imports --- src/pages/friend.page.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index e247f61..b770366 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,6 +1,5 @@ import { useEffect, useState } from "react"; import FriendRequest from "../types/friend-request"; -import { dateFormater } from "../utils/dateFormater"; import FriendRequestCard from "../components/friend-request-card.component"; import { fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loader/loader.component"; From c824926330b58ff25e2c698164deb31292172053 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 09:29:45 +0100 Subject: [PATCH 040/118] chore : follow project files nomination convention --- src/components/Form/{AuthForm.tsx => auth.form.tsx} | 2 +- .../{GuestRoute.tsx => Guard/guest-route.guard.tsx} | 2 +- .../procteded-route.guard.tsx} | 2 +- src/index.tsx | 10 +++++----- src/pages/{HomePage.tsx => home.page.tsx} | 0 src/pages/{LoginPage.tsx => login.page.tsx} | 4 ++-- src/pages/{RegisterPage.tsx => register.page.tsx} | 4 ++-- src/services/auth.service.ts | 2 +- src/types/{userDto.ts => user-dto.ts} | 0 9 files changed, 13 insertions(+), 13 deletions(-) rename src/components/Form/{AuthForm.tsx => auth.form.tsx} (95%) rename src/components/{GuestRoute.tsx => Guard/guest-route.guard.tsx} (88%) rename src/components/{ProtectedRoute.tsx => Guard/procteded-route.guard.tsx} (88%) rename src/pages/{HomePage.tsx => home.page.tsx} (100%) rename src/pages/{LoginPage.tsx => login.page.tsx} (82%) rename src/pages/{RegisterPage.tsx => register.page.tsx} (82%) rename src/types/{userDto.ts => user-dto.ts} (100%) diff --git a/src/components/Form/AuthForm.tsx b/src/components/Form/auth.form.tsx similarity index 95% rename from src/components/Form/AuthForm.tsx rename to src/components/Form/auth.form.tsx index 409ceac..0cfebb6 100644 --- a/src/components/Form/AuthForm.tsx +++ b/src/components/Form/auth.form.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; -import { UserDTO } from "../../types/userDto"; +import { UserDTO } from "../../types/user-dto"; interface AuthFormProps { submitFn: (data: UserDTO) => Promise; diff --git a/src/components/GuestRoute.tsx b/src/components/Guard/guest-route.guard.tsx similarity index 88% rename from src/components/GuestRoute.tsx rename to src/components/Guard/guest-route.guard.tsx index 9e0b19d..da9c69c 100644 --- a/src/components/GuestRoute.tsx +++ b/src/components/Guard/guest-route.guard.tsx @@ -1,5 +1,5 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../stores/userStore"; +import { useUserStore } from "../../stores/userStore"; import { useEffect } from "react"; export default function GuestRoute() { diff --git a/src/components/ProtectedRoute.tsx b/src/components/Guard/procteded-route.guard.tsx similarity index 88% rename from src/components/ProtectedRoute.tsx rename to src/components/Guard/procteded-route.guard.tsx index f1f1a4f..c37f718 100644 --- a/src/components/ProtectedRoute.tsx +++ b/src/components/Guard/procteded-route.guard.tsx @@ -1,5 +1,5 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../stores/userStore"; +import { useUserStore } from "../../stores/userStore"; import { useEffect } from "react"; export default function ProtectedRoute() { diff --git a/src/index.tsx b/src/index.tsx index be87b0b..96ecdb2 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,15 +2,15 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; import reportWebVitals from "./reportWebVitals"; -import HomePage from "./pages/HomePage"; +import HomePage from "./pages/home.page"; import ChatPage from "./pages/ChatPage"; import FriendPage from "./pages/FriendPage"; import App from "./App"; import "./index.css"; -import RegisterPage from "./pages/RegisterPage"; -import LoginPage from "./pages/LoginPage"; -import ProtectedRoute from "./components/ProtectedRoute"; -import GuestRoute from "./components/GuestRoute"; +import RegisterPage from "./pages/register.page"; +import LoginPage from "./pages/login.page"; +import ProtectedRoute from "./components/Guard/procteded-route.guard"; +import GuestRoute from "./components/Guard/guest-route.guard"; const router = createBrowserRouter([ { diff --git a/src/pages/HomePage.tsx b/src/pages/home.page.tsx similarity index 100% rename from src/pages/HomePage.tsx rename to src/pages/home.page.tsx diff --git a/src/pages/LoginPage.tsx b/src/pages/login.page.tsx similarity index 82% rename from src/pages/LoginPage.tsx rename to src/pages/login.page.tsx index ed8cb1d..2ef0c18 100644 --- a/src/pages/LoginPage.tsx +++ b/src/pages/login.page.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; -import AuthForm from "../components/Form/AuthForm"; -import { UserDTO } from "../types/userDto"; +import AuthForm from "../components/Form/auth.form"; +import { UserDTO } from "../types/user-dto"; export default function LoginPage() { const navigate = useNavigate(); diff --git a/src/pages/RegisterPage.tsx b/src/pages/register.page.tsx similarity index 82% rename from src/pages/RegisterPage.tsx rename to src/pages/register.page.tsx index 177d2f1..7ee2685 100644 --- a/src/pages/RegisterPage.tsx +++ b/src/pages/register.page.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; -import AuthForm from "../components/Form/AuthForm"; -import { UserDTO } from "../types/userDto"; +import AuthForm from "../components/Form/auth.form"; +import { UserDTO } from "../types/user-dto"; export default function RegisterPage() { const navigate = useNavigate(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 972734e..0247dd5 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { UserDTO } from "../types/userDto"; +import { UserDTO } from "../types/user-dto"; export async function loginUser(data: UserDTO) { const response = await fetch( diff --git a/src/types/userDto.ts b/src/types/user-dto.ts similarity index 100% rename from src/types/userDto.ts rename to src/types/user-dto.ts From 880748a2c1135801ffc5742e0fb1c575954faeb5 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:30:35 +0100 Subject: [PATCH 041/118] export type update --- src/pages/friend.page.tsx | 2 +- src/types/friend-request.ts | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index b770366..f3278c4 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,8 +1,8 @@ import { useEffect, useState } from "react"; -import FriendRequest from "../types/friend-request"; import FriendRequestCard from "../components/friend-request-card.component"; import { fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loader/loader.component"; +import { FriendRequest } from "../types/friend-request"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); diff --git a/src/types/friend-request.ts b/src/types/friend-request.ts index f4c3b6b..e57db80 100644 --- a/src/types/friend-request.ts +++ b/src/types/friend-request.ts @@ -1,7 +1,5 @@ -interface FriendRequest { +export interface FriendRequest { id: number; senderId: string; requestedAt: string; -} - -export default FriendRequest \ No newline at end of file +} \ No newline at end of file From d975da46baf6cf46dd072e91ec7c2568f4238024 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 09:33:46 +0100 Subject: [PATCH 042/118] chore : plurial to components folder names --- src/components/{Form => Forms}/auth.form.tsx | 0 src/components/{Guard => Guards}/guest-route.guard.tsx | 0 src/components/{Guard => Guards}/procteded-route.guard.tsx | 0 src/pages/login.page.tsx | 2 +- src/pages/register.page.tsx | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename src/components/{Form => Forms}/auth.form.tsx (100%) rename src/components/{Guard => Guards}/guest-route.guard.tsx (100%) rename src/components/{Guard => Guards}/procteded-route.guard.tsx (100%) diff --git a/src/components/Form/auth.form.tsx b/src/components/Forms/auth.form.tsx similarity index 100% rename from src/components/Form/auth.form.tsx rename to src/components/Forms/auth.form.tsx diff --git a/src/components/Guard/guest-route.guard.tsx b/src/components/Guards/guest-route.guard.tsx similarity index 100% rename from src/components/Guard/guest-route.guard.tsx rename to src/components/Guards/guest-route.guard.tsx diff --git a/src/components/Guard/procteded-route.guard.tsx b/src/components/Guards/procteded-route.guard.tsx similarity index 100% rename from src/components/Guard/procteded-route.guard.tsx rename to src/components/Guards/procteded-route.guard.tsx diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index 2ef0c18..57558d2 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; -import AuthForm from "../components/Form/auth.form"; +import AuthForm from "../components/Forms/auth.form"; import { UserDTO } from "../types/user-dto"; export default function LoginPage() { diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index 7ee2685..3fb03f4 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; -import AuthForm from "../components/Form/auth.form"; +import AuthForm from "../components/Forms/auth.form"; import { UserDTO } from "../types/user-dto"; export default function RegisterPage() { From d4e75a67c7d00c4a0aa8139244f80666a59affab Mon Sep 17 00:00:00 2001 From: Val Date: Tue, 5 Nov 2024 22:13:28 +0100 Subject: [PATCH 043/118] feat : add current user friend list --- src/pages/home.page.tsx | 27 ++++++++++++++++++++++++++- src/services/friends.service.ts | 18 ++++++++++++++++++ src/types/friends.ts | 5 +++++ 3 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/services/friends.service.ts create mode 100644 src/types/friends.ts diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 3081f29..e6ee440 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,7 +1,32 @@ +import { useEffect, useState } from "react"; +import { Friend } from "../types/friends"; +import { getUserFriends } from "../services/friends.service"; + export default function HomePage() { + const [friends, setFriends] = useState([]); + + const fetchFriends = async () => { + const friendList = await getUserFriends(); + setFriends(friendList); + }; + + const sortedByDateFriendsList = friends.sort((a, b) => { + return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(); + }); + useEffect(() => { + fetchFriends(); + }, []); + return (

Home Page

+
+
    + {sortedByDateFriendsList.map((friend) => ( +
  • {friend.username}
  • + ))} +
+
); -} \ No newline at end of file +} diff --git a/src/services/friends.service.ts b/src/services/friends.service.ts new file mode 100644 index 0000000..470fc85 --- /dev/null +++ b/src/services/friends.service.ts @@ -0,0 +1,18 @@ +import { Friend } from "../types/friends"; + +export const getUserFriends = async (): Promise => { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/social/friends`, + { + method: "GET", + credentials: "include", + } + ); + if (!response.ok) { + const { message } = await response.json(); + console.error(message); + return []; + } + + return (await response.json()) as Friend[]; +}; diff --git a/src/types/friends.ts b/src/types/friends.ts new file mode 100644 index 0000000..4d5256c --- /dev/null +++ b/src/types/friends.ts @@ -0,0 +1,5 @@ +export interface Friend { + userId: string; + username: string; + startedAt: string; +} From 1701a074fd5c75bc1de8712f370b920f50b48b9f Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:36:14 +0100 Subject: [PATCH 044/118] update types import --- src/components/friend-request-card.component.tsx | 2 +- src/services/friend-request.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index 57bfcbc..e2107b6 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -1,4 +1,4 @@ -import FriendRequest from "../types/friend-request"; +import { FriendRequest } from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; export default function FriendRequestCard(request: FriendRequest) { diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 0d95b87..28a2e3f 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -1,4 +1,4 @@ -import FriendRequest from "../types/friend-request"; +import { FriendRequest } from "../types/friend-request"; export async function fetchFriendRequests() { const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-requests`, { From 0277a3a9e71951070004deb9e160b9a75e496ad1 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:38:10 +0100 Subject: [PATCH 045/118] update loader --- src/components/{loader => loaders}/loader.component.tsx | 0 src/components/{loader => loaders}/loader.css | 0 src/pages/friend.page.tsx | 2 +- 3 files changed, 1 insertion(+), 1 deletion(-) rename src/components/{loader => loaders}/loader.component.tsx (100%) rename src/components/{loader => loaders}/loader.css (100%) diff --git a/src/components/loader/loader.component.tsx b/src/components/loaders/loader.component.tsx similarity index 100% rename from src/components/loader/loader.component.tsx rename to src/components/loaders/loader.component.tsx diff --git a/src/components/loader/loader.css b/src/components/loaders/loader.css similarity index 100% rename from src/components/loader/loader.css rename to src/components/loaders/loader.css diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index f3278c4..b146111 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import FriendRequestCard from "../components/friend-request-card.component"; import { fetchFriendRequests } from "../services/friend-request.service"; -import Loader from "../components/loader/loader.component"; +import Loader from "../components/loaders/loader.component"; import { FriendRequest } from "../types/friend-request"; export default function FriendPage() { From 1494a707d2b73eeb42014da7910c62e1b0a61c0c Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 09:38:55 +0100 Subject: [PATCH 046/118] feat : add plurial to router guards components import --- src/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 96ecdb2..e680471 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,8 +9,8 @@ import App from "./App"; import "./index.css"; import RegisterPage from "./pages/register.page"; import LoginPage from "./pages/login.page"; -import ProtectedRoute from "./components/Guard/procteded-route.guard"; -import GuestRoute from "./components/Guard/guest-route.guard"; +import ProtectedRoute from "./components/Guards/procteded-route.guard"; +import GuestRoute from "./components/Guards/guest-route.guard"; const router = createBrowserRouter([ { From b81b16ce637342114aac2f6ba5bd86cc3921a9f7 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 09:46:04 +0100 Subject: [PATCH 047/118] chore : remove uppercase from folder names --- src/index.tsx | 4 ++-- src/pages/login.page.tsx | 2 +- src/pages/register.page.tsx | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 96ecdb2..84b08ab 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,8 +9,8 @@ import App from "./App"; import "./index.css"; import RegisterPage from "./pages/register.page"; import LoginPage from "./pages/login.page"; -import ProtectedRoute from "./components/Guard/procteded-route.guard"; -import GuestRoute from "./components/Guard/guest-route.guard"; +import ProtectedRoute from "./components/guards/procteded-route.guard"; +import GuestRoute from "./components/guards/guest-route.guard"; const router = createBrowserRouter([ { diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index 57558d2..aa2ae28 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; -import AuthForm from "../components/Forms/auth.form"; +import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../types/user-dto"; export default function LoginPage() { diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index 3fb03f4..f461b67 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; -import AuthForm from "../components/Forms/auth.form"; +import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../types/user-dto"; export default function RegisterPage() { From 8c3897d20267648ed8161c82f147c0eec7191753 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 09:49:43 +0100 Subject: [PATCH 048/118] chore : separate function logics with spaces --- src/components/Guards/guest-route.guard.tsx | 2 ++ src/components/Guards/procteded-route.guard.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/components/Guards/guest-route.guard.tsx b/src/components/Guards/guest-route.guard.tsx index da9c69c..67b56a7 100644 --- a/src/components/Guards/guest-route.guard.tsx +++ b/src/components/Guards/guest-route.guard.tsx @@ -5,12 +5,14 @@ import { useEffect } from "react"; export default function GuestRoute() { const checkUserAuth = useUserStore((state) => state.checkUserAuth); const navigate = useNavigate(); + const checkAuth = async () => { const isAuth = await checkUserAuth(); if (isAuth) { navigate("/"); } }; + useEffect(() => { checkAuth(); }, []); diff --git a/src/components/Guards/procteded-route.guard.tsx b/src/components/Guards/procteded-route.guard.tsx index c37f718..47e7407 100644 --- a/src/components/Guards/procteded-route.guard.tsx +++ b/src/components/Guards/procteded-route.guard.tsx @@ -5,12 +5,14 @@ import { useEffect } from "react"; export default function ProtectedRoute() { const checkUserAuth = useUserStore((state) => state.checkUserAuth); const navigate = useNavigate(); + const checkAuth = async () => { const isAuth = await checkUserAuth(); if (!isAuth) { navigate("/login"); } }; + useEffect(() => { checkAuth(); }, []); From e6e18f414c0e74caf1ce53a1dc270a930cc1a68e Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 09:53:03 +0100 Subject: [PATCH 049/118] :art: rename folder and store file --- package-lock.json | 49 ++++++++++++++++++- src/components/Guards/guest-route.guard.tsx | 2 +- .../Guards/procteded-route.guard.tsx | 2 +- src/stores/{userStore.ts => user.store.ts} | 0 4 files changed, 50 insertions(+), 3 deletions(-) rename src/stores/{userStore.ts => user.store.ts} (100%) diff --git a/package-lock.json b/package-lock.json index b4446c4..12e01a8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,10 +17,12 @@ "@types/react-dom": "^18.3.1", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hook-form": "^7.53.1", "react-router-dom": "^6.27.0", "react-scripts": "5.0.1", "typescript": "^4.9.5", - "web-vitals": "^2.1.4" + "web-vitals": "^2.1.4", + "zustand": "^5.0.1" }, "devDependencies": { "tailwindcss": "^3.4.14" @@ -13906,6 +13908,22 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "license": "MIT" }, + "node_modules/react-hook-form": { + "version": "7.53.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.1.tgz", + "integrity": "sha512-6aiQeBda4zjcuaugWvim9WsGqisoUk+etmFEsSUMm451/Ic8L/UAb7sRtMj3V+Hdzm6mMjU1VhiSzYUZeBm0Vg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -17472,6 +17490,35 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zustand": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.1.tgz", + "integrity": "sha512-pRET7Lao2z+n5R/HduXMio35TncTlSW68WsYBq2Lg1ASspsNGjpwLAsij3RpouyV6+kHMwwwzP0bZPD70/Jx/w==", + "license": "MIT", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/src/components/Guards/guest-route.guard.tsx b/src/components/Guards/guest-route.guard.tsx index 67b56a7..bfbc6a3 100644 --- a/src/components/Guards/guest-route.guard.tsx +++ b/src/components/Guards/guest-route.guard.tsx @@ -1,5 +1,5 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../../stores/userStore"; +import { useUserStore } from "../../stores/user.store"; import { useEffect } from "react"; export default function GuestRoute() { diff --git a/src/components/Guards/procteded-route.guard.tsx b/src/components/Guards/procteded-route.guard.tsx index 47e7407..ef940fb 100644 --- a/src/components/Guards/procteded-route.guard.tsx +++ b/src/components/Guards/procteded-route.guard.tsx @@ -1,5 +1,5 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../../stores/userStore"; +import { useUserStore } from "../../stores/user.store"; import { useEffect } from "react"; export default function ProtectedRoute() { diff --git a/src/stores/userStore.ts b/src/stores/user.store.ts similarity index 100% rename from src/stores/userStore.ts rename to src/stores/user.store.ts From be26ee4eacae1901d67675a15c51aba0de252f1b Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:06:09 +0100 Subject: [PATCH 050/118] refactor : create dto folder & move checkAuth to auth service --- src/components/Forms/auth.form.tsx | 2 +- src/components/Guards/guest-route.guard.tsx | 3 +-- src/components/Guards/procteded-route.guard.tsx | 3 +-- src/{types/user-dto.ts => dtos/user.dto.ts} | 0 src/pages/login.page.tsx | 2 +- src/pages/register.page.tsx | 2 +- src/services/auth.service.ts | 13 ++++++++++++- src/stores/user.store.ts | 13 ------------- 8 files changed, 17 insertions(+), 21 deletions(-) rename src/{types/user-dto.ts => dtos/user.dto.ts} (100%) diff --git a/src/components/Forms/auth.form.tsx b/src/components/Forms/auth.form.tsx index 0cfebb6..7c75bbe 100644 --- a/src/components/Forms/auth.form.tsx +++ b/src/components/Forms/auth.form.tsx @@ -1,6 +1,6 @@ import { useState } from "react"; import { useForm, SubmitHandler } from "react-hook-form"; -import { UserDTO } from "../../types/user-dto"; +import { UserDTO } from "../../dtos/user.dto"; interface AuthFormProps { submitFn: (data: UserDTO) => Promise; diff --git a/src/components/Guards/guest-route.guard.tsx b/src/components/Guards/guest-route.guard.tsx index bfbc6a3..e899d7a 100644 --- a/src/components/Guards/guest-route.guard.tsx +++ b/src/components/Guards/guest-route.guard.tsx @@ -1,9 +1,8 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../../stores/user.store"; import { useEffect } from "react"; +import { checkUserAuth } from "../../services/auth.service"; export default function GuestRoute() { - const checkUserAuth = useUserStore((state) => state.checkUserAuth); const navigate = useNavigate(); const checkAuth = async () => { diff --git a/src/components/Guards/procteded-route.guard.tsx b/src/components/Guards/procteded-route.guard.tsx index ef940fb..1279d36 100644 --- a/src/components/Guards/procteded-route.guard.tsx +++ b/src/components/Guards/procteded-route.guard.tsx @@ -1,9 +1,8 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useUserStore } from "../../stores/user.store"; import { useEffect } from "react"; +import { checkUserAuth } from "../../services/auth.service"; export default function ProtectedRoute() { - const checkUserAuth = useUserStore((state) => state.checkUserAuth); const navigate = useNavigate(); const checkAuth = async () => { diff --git a/src/types/user-dto.ts b/src/dtos/user.dto.ts similarity index 100% rename from src/types/user-dto.ts rename to src/dtos/user.dto.ts diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index aa2ae28..d60ff3e 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; import AuthForm from "../components/forms/auth.form"; -import { UserDTO } from "../types/user-dto"; +import { UserDTO } from "../dtos/user.dto"; export default function LoginPage() { const navigate = useNavigate(); diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index f461b67..ba58971 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -1,7 +1,7 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; import AuthForm from "../components/forms/auth.form"; -import { UserDTO } from "../types/user-dto"; +import { UserDTO } from "../dtos/user.dto"; export default function RegisterPage() { const navigate = useNavigate(); diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 0247dd5..a31bca7 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,4 @@ -import { UserDTO } from "../types/user-dto"; +import { UserDTO } from "../dtos/user.dto"; export async function loginUser(data: UserDTO) { const response = await fetch( @@ -49,3 +49,14 @@ export async function logoutUser() { throw new Error(message); } } + +export async function checkUserAuth() { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/me`, + { + credentials: "include", + } + ); + + return response.ok ? true : false; +} diff --git a/src/stores/user.store.ts b/src/stores/user.store.ts index 1ffe18b..3792cac 100644 --- a/src/stores/user.store.ts +++ b/src/stores/user.store.ts @@ -8,23 +8,10 @@ type State = { type Action = { clearUser: () => void; updateUser: (newUser: State) => void; - checkUserAuth: () => Promise; }; export const useUserStore = create((set) => ({ username: "", userId: "", clearUser: () => set({ username: "", userId: "" }), updateUser: (newUser) => set(newUser), - checkUserAuth: async () => { - const response = await fetch( - `${process.env.REACT_APP_API_BASE_URL}/auth/me`, - { - credentials: "include", - } - ); - if (!response.ok) { - return false; - } - return true; - }, })); From c56a414ed1d66baa936d7a362fd7baa7e4c490bf Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:26:22 +0100 Subject: [PATCH 051/118] fix : INTENTIONAL errors to updates folders names --- src/components/{Forms => formss}/auth.form.tsx | 0 src/components/{Guards => guardss}/guest-route.guard.tsx | 0 .../{Guards => guardss}/procteded-route.guard.tsx | 0 src/index.tsx | 6 ++---- src/pages/login.page.tsx | 2 +- src/pages/register.page.tsx | 2 +- 6 files changed, 4 insertions(+), 6 deletions(-) rename src/components/{Forms => formss}/auth.form.tsx (100%) rename src/components/{Guards => guardss}/guest-route.guard.tsx (100%) rename src/components/{Guards => guardss}/procteded-route.guard.tsx (100%) diff --git a/src/components/Forms/auth.form.tsx b/src/components/formss/auth.form.tsx similarity index 100% rename from src/components/Forms/auth.form.tsx rename to src/components/formss/auth.form.tsx diff --git a/src/components/Guards/guest-route.guard.tsx b/src/components/guardss/guest-route.guard.tsx similarity index 100% rename from src/components/Guards/guest-route.guard.tsx rename to src/components/guardss/guest-route.guard.tsx diff --git a/src/components/Guards/procteded-route.guard.tsx b/src/components/guardss/procteded-route.guard.tsx similarity index 100% rename from src/components/Guards/procteded-route.guard.tsx rename to src/components/guardss/procteded-route.guard.tsx diff --git a/src/index.tsx b/src/index.tsx index e22ed7f..f751f45 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { createBrowserRouter, RouterProvider } from "react-router-dom"; - import reportWebVitals from "./reportWebVitals"; import App from "./App"; import "./index.css"; @@ -12,8 +11,8 @@ import HomePage from "./pages/home.page"; import ChatListPage from "./pages/chat-list.page"; import ChatPage from "./pages/chat.page"; import FriendPage from "./pages/friend.page"; -import ProtectedRoute from "./components/guards/procteded-route.guard"; -import GuestRoute from "./components/guards/guest-route.guard"; +import ProtectedRoute from "./components/guardss/procteded-route.guard"; +import GuestRoute from "./components/guardss/guest-route.guard"; const router = createBrowserRouter([ { @@ -54,7 +53,6 @@ const router = createBrowserRouter([ path: "/login", element: , }, - ], }, ]); diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index d60ff3e..60f4aa2 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; -import AuthForm from "../components/forms/auth.form"; +import AuthForm from "../components/formss/auth.form"; import { UserDTO } from "../dtos/user.dto"; export default function LoginPage() { diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index ba58971..5a49212 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; -import AuthForm from "../components/forms/auth.form"; +import AuthForm from "../components/formss/auth.form"; import { UserDTO } from "../dtos/user.dto"; export default function RegisterPage() { From 430c801ab7800351d2f1f797a314160aa8e9d423 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:27:08 +0100 Subject: [PATCH 052/118] fix : repair previous error to have correct names --- src/components/{formss => forms}/auth.form.tsx | 0 src/components/{guardss => guards}/guest-route.guard.tsx | 0 src/components/{guardss => guards}/procteded-route.guard.tsx | 0 src/pages/login.page.tsx | 2 +- src/pages/register.page.tsx | 2 +- 5 files changed, 2 insertions(+), 2 deletions(-) rename src/components/{formss => forms}/auth.form.tsx (100%) rename src/components/{guardss => guards}/guest-route.guard.tsx (100%) rename src/components/{guardss => guards}/procteded-route.guard.tsx (100%) diff --git a/src/components/formss/auth.form.tsx b/src/components/forms/auth.form.tsx similarity index 100% rename from src/components/formss/auth.form.tsx rename to src/components/forms/auth.form.tsx diff --git a/src/components/guardss/guest-route.guard.tsx b/src/components/guards/guest-route.guard.tsx similarity index 100% rename from src/components/guardss/guest-route.guard.tsx rename to src/components/guards/guest-route.guard.tsx diff --git a/src/components/guardss/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx similarity index 100% rename from src/components/guardss/procteded-route.guard.tsx rename to src/components/guards/procteded-route.guard.tsx diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index 60f4aa2..d60ff3e 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { loginUser } from "../services/auth.service"; -import AuthForm from "../components/formss/auth.form"; +import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../dtos/user.dto"; export default function LoginPage() { diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index 5a49212..ba58971 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -1,6 +1,6 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; -import AuthForm from "../components/formss/auth.form"; +import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../dtos/user.dto"; export default function RegisterPage() { From 8bae91115a52d145603383019581a1bad3e1fb1c Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:28:44 +0100 Subject: [PATCH 053/118] fix : correct import names --- src/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index f751f45..34157e9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -11,8 +11,8 @@ import HomePage from "./pages/home.page"; import ChatListPage from "./pages/chat-list.page"; import ChatPage from "./pages/chat.page"; import FriendPage from "./pages/friend.page"; -import ProtectedRoute from "./components/guardss/procteded-route.guard"; -import GuestRoute from "./components/guardss/guest-route.guard"; +import ProtectedRoute from "./components/guards/procteded-route.guard"; +import GuestRoute from "./components/guards/guest-route.guard"; const router = createBrowserRouter([ { From 4237ba57ddf24dd3348388269ae5bd3b1444d05e Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 10:56:23 +0100 Subject: [PATCH 054/118] feat: store messages to zustand store --- src/components/{Forms => forms}/auth.form.tsx | 0 .../{Guards => guards}/guest-route.guard.tsx | 0 .../procteded-route.guard.tsx | 0 src/components/loaders/messages.loader.tsx | 2 +- src/dtos/message.dto.ts | 5 +++ src/pages/chat.page.tsx | 36 ++++++++----------- src/services/message.service.ts | 10 +++--- src/stores/message.store.ts | 10 ++++-- src/types/message.ts | 4 +-- 9 files changed, 34 insertions(+), 33 deletions(-) rename src/components/{Forms => forms}/auth.form.tsx (100%) rename src/components/{Guards => guards}/guest-route.guard.tsx (100%) rename src/components/{Guards => guards}/procteded-route.guard.tsx (100%) create mode 100644 src/dtos/message.dto.ts diff --git a/src/components/Forms/auth.form.tsx b/src/components/forms/auth.form.tsx similarity index 100% rename from src/components/Forms/auth.form.tsx rename to src/components/forms/auth.form.tsx diff --git a/src/components/Guards/guest-route.guard.tsx b/src/components/guards/guest-route.guard.tsx similarity index 100% rename from src/components/Guards/guest-route.guard.tsx rename to src/components/guards/guest-route.guard.tsx diff --git a/src/components/Guards/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx similarity index 100% rename from src/components/Guards/procteded-route.guard.tsx rename to src/components/guards/procteded-route.guard.tsx diff --git a/src/components/loaders/messages.loader.tsx b/src/components/loaders/messages.loader.tsx index c7fa803..8c5f891 100644 --- a/src/components/loaders/messages.loader.tsx +++ b/src/components/loaders/messages.loader.tsx @@ -11,7 +11,7 @@ export default function MessagesLoader({ receiverId, children }: LoaderProps) { useEffect(() => { if (!receiverId) { - navigate('/chats'); + navigate('/'); } }, [receiverId, navigate]); diff --git a/src/dtos/message.dto.ts b/src/dtos/message.dto.ts new file mode 100644 index 0000000..e4bb05c --- /dev/null +++ b/src/dtos/message.dto.ts @@ -0,0 +1,5 @@ +export interface MessageDTO { + id: string; + content: string; + receiverId: string; +} \ No newline at end of file diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index f64fbb0..d5b41b5 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -1,36 +1,36 @@ import {useNavigate, useParams} from "react-router-dom"; import { SubmitHandler, useForm } from "react-hook-form"; -import { useEffect, useState } from "react"; +import { useEffect } from "react"; import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; +import { MessageDTO } from "../dtos/message.dto"; export default function ChatPage() { type FormInputs = { - message: string; + content: string; } const navigate = useNavigate(); const { receiverId } = useParams(); - const { messages, addMessage } = useMessageStore(); - - const [loadedMessages, setLoadedMessages] = useState([]); + const { messages, setMessages, addMessage } = useMessageStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { - message: "" + content: "" } }) - const onSubmit: SubmitHandler = async (data) => { + const onSubmit: SubmitHandler = async (input) => { if (!receiverId) return; - addMessage(data.message); - await sendMessage(receiverId, data.message); + const message: MessageDTO = { id: crypto.randomUUID(), content: input.content, receiverId: receiverId }; + addMessage(message); + await sendMessage(message); reset(); } @@ -39,16 +39,15 @@ export default function ChatPage() { const loadMessages = async (): Promise => { try { const messages = await fetchMessages(receiverId); - setLoadedMessages(messages); + setMessages(messages); } catch (error) { navigate('/chats'); } }; loadMessages(); - const handleNewMessage = (data: Message) => { - console.log("Nouveau message reçu :", data); - setLoadedMessages((prevMessages) => [...prevMessages, data]); + const handleNewMessage = (message: Message) => { + addMessage(message); }; const eventSource = eventFetchMessages(handleNewMessage); @@ -65,21 +64,14 @@ export default function ChatPage() {

Chat Page: {receiverId}

- {loadedMessages.map((message, index) => ( + {messages.map((message, index) => (
{message.content}
))}
-

--------------------------

- -
{messages.map((message, index) => -
{message}
- )}
- -
+ placeholder="Tapez votre message ici" {...register('content', { required: true })} />
diff --git a/src/services/message.service.ts b/src/services/message.service.ts index da5f7b3..49a77d0 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -1,16 +1,16 @@ import Message from "../types/message"; +import { MessageDTO } from "../dtos/message.dto"; -export const sendMessage = async (receiverId: string, content: string): Promise => { - const messageId = crypto.randomUUID(); - await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { +export const sendMessage = async (message: MessageDTO): Promise => { + await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, credentials: 'include', body: JSON.stringify({ - receiverId, - content, + receiverId: message.receiverId, + content: message.content, }), }) } diff --git a/src/stores/message.store.ts b/src/stores/message.store.ts index 45c3bef..8d843c9 100644 --- a/src/stores/message.store.ts +++ b/src/stores/message.store.ts @@ -1,11 +1,15 @@ import { create } from "zustand/react"; +import Message from "../types/message"; + type MessageStore = { - messages: string[]; - addMessage: (message: string) => void; + messages: Message[]; + setMessages: (messages: Message[]) => void; + addMessage: (message: Message) => void; } export const useMessageStore = create((set) => ({ messages: [], - addMessage: (message: string) => set((state) => ({ messages: [...state.messages, message] })), + setMessages: (messages: Message[]) => set({ messages }), + addMessage: (message: Message) => set((state) => ({ messages: [...state.messages, message] })), })) \ No newline at end of file diff --git a/src/types/message.ts b/src/types/message.ts index 7eaaa35..fc185d0 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,6 +1,6 @@ export default interface Message { id: string, - emitterId: string, + emitterId?: string, content: string, - sentAt: Date, + sentAt?: Date, } \ No newline at end of file From 73113019f9263959519ed2d2b72e7061f2919515 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:56:58 +0100 Subject: [PATCH 055/118] feat : redirect user to his friend chat --- src/pages/home.page.tsx | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index e6ee440..5acee76 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,6 +1,7 @@ import { useEffect, useState } from "react"; import { Friend } from "../types/friends"; import { getUserFriends } from "../services/friends.service"; +import { useNavigate } from "react-router-dom"; export default function HomePage() { const [friends, setFriends] = useState([]); @@ -17,13 +18,27 @@ export default function HomePage() { fetchFriends(); }, []); + const navigate = useNavigate(); + const redirectUserToChatPage = ( + event: React.MouseEvent, + userId: string + ) => { + event.preventDefault(); + navigate(`/chats/${userId}`); + }; + return (

Home Page

    {sortedByDateFriendsList.map((friend) => ( -
  • {friend.username}
  • +
  • redirectUserToChatPage(e, friend.userId)} + key={friend.userId} + > + {friend.username} +
  • ))}
From 69d0ee0381a04751762734ed2d689c45718cc292 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 10:59:02 +0100 Subject: [PATCH 056/118] chore : remove plurial --- src/pages/home.page.tsx | 4 ++-- src/services/{friends.service.ts => friend.service.ts} | 2 +- src/types/{friends.ts => friend.ts} | 0 3 files changed, 3 insertions(+), 3 deletions(-) rename src/services/{friends.service.ts => friend.service.ts} (90%) rename src/types/{friends.ts => friend.ts} (100%) diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 5acee76..87fda1f 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; -import { Friend } from "../types/friends"; -import { getUserFriends } from "../services/friends.service"; +import { Friend } from "../types/friend"; +import { getUserFriends } from "../services/friend.service"; import { useNavigate } from "react-router-dom"; export default function HomePage() { diff --git a/src/services/friends.service.ts b/src/services/friend.service.ts similarity index 90% rename from src/services/friends.service.ts rename to src/services/friend.service.ts index 470fc85..cee7e9c 100644 --- a/src/services/friends.service.ts +++ b/src/services/friend.service.ts @@ -1,4 +1,4 @@ -import { Friend } from "../types/friends"; +import { Friend } from "../types/friend"; export const getUserFriends = async (): Promise => { const response = await fetch( diff --git a/src/types/friends.ts b/src/types/friend.ts similarity index 100% rename from src/types/friends.ts rename to src/types/friend.ts From ab3d691a04d16addcb54b439b4c5891a68736d30 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 11:20:27 +0100 Subject: [PATCH 057/118] feat : add friend store --- src/stores/friend.store.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/stores/friend.store.ts diff --git a/src/stores/friend.store.ts b/src/stores/friend.store.ts new file mode 100644 index 0000000..f694404 --- /dev/null +++ b/src/stores/friend.store.ts @@ -0,0 +1,18 @@ +import { create } from "zustand/react"; +import { Friend } from "../types/friend"; + +type States = { + friends: Friend[]; +}; + +type Actions = { + setFriends: (friends: Friend[]) => void; + addFriend: (friend: Friend) => void; +}; + +export const useFriendStore = create((set) => ({ + friends: [], + setFriends: (friends: Friend[]) => set({ friends }), + addFriend: (friend: Friend) => + set((state) => ({ friends: [...state.friends, friend] })), +})); From 30d867d060434f6327f1874aa22d115521c03299 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 11:20:47 +0100 Subject: [PATCH 058/118] feat : only fetch friends when store is empty --- src/pages/home.page.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 87fda1f..50e2f3f 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -1,21 +1,24 @@ -import { useEffect, useState } from "react"; -import { Friend } from "../types/friend"; +import { useEffect } from "react"; import { getUserFriends } from "../services/friend.service"; import { useNavigate } from "react-router-dom"; +import { useFriendStore } from "../stores/friend.store"; export default function HomePage() { - const [friends, setFriends] = useState([]); + const { friends, setFriends } = useFriendStore(); - const fetchFriends = async () => { - const friendList = await getUserFriends(); - setFriends(friendList); + const loadFriends = async () => { + if (friends.length === 0) { + const friends = await getUserFriends(); + setFriends(friends); + } }; const sortedByDateFriendsList = friends.sort((a, b) => { return new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime(); }); + useEffect(() => { - fetchFriends(); + loadFriends(); }, []); const navigate = useNavigate(); From c3c17e16b16b127cb8e7532a46778d689815d8ae Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 11:29:05 +0100 Subject: [PATCH 059/118] :sparkles: new friend request notification feature --- src/pages/friend.page.tsx | 32 ++++++++++++++++++-------- src/services/friend-request.service.ts | 13 +++++++++++ 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index b146111..7bad046 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,6 +1,6 @@ import { useEffect, useState } from "react"; import FriendRequestCard from "../components/friend-request-card.component"; -import { fetchFriendRequests } from "../services/friend-request.service"; +import { eventFetchFriendRequests, fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loaders/loader.component"; import { FriendRequest } from "../types/friend-request"; @@ -9,13 +9,28 @@ export default function FriendPage() { const [loading, setLoading] = useState(true); useEffect(() => { - loadFriendRequests(); - }, []); - - async function loadFriendRequests() { - const requests = await fetchFriendRequests(); - setFriendRequests(requests); - setLoading(false); + loadInitialRequests(); + + const handleNewFriendRequest = (data: FriendRequest) => { + setFriendRequests(prevRequests => {return [data, ...prevRequests];}); + }; + + const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + + return () => { + eventSource.close(); + }; + }, []); + + async function loadInitialRequests() { + try { + const requests = await fetchFriendRequests(); + setFriendRequests(requests); + } catch (error) { + console.error("Error loading friend requests:", error); + } finally { + setLoading(false); + } } return ( @@ -26,7 +41,6 @@ export default function FriendPage() {
- Friends Requests {friendRequests.map((request) => ( diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 28a2e3f..0761943 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -9,4 +9,17 @@ export async function fetchFriendRequests() { return friendRequests.map((request: FriendRequest) => ({ ...request, })); +} + +export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest) => void) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener('friend-request-received', (event) => { + const data = JSON.parse(event.data); + onRequestReceived(data); + }) + + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; } \ No newline at end of file From 6481c62fc3dff907d0c67a9c20cf9f87bc87948a Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 12:07:11 +0100 Subject: [PATCH 060/118] feat: send error on send message if user not friends --- src/dtos/message.dto.ts | 2 ++ src/pages/chat.page.tsx | 20 ++++++++++++++++---- src/services/message.service.ts | 8 ++++++-- src/stores/message.store.ts | 7 +++++++ src/types/message.ts | 4 ++-- 5 files changed, 33 insertions(+), 8 deletions(-) diff --git a/src/dtos/message.dto.ts b/src/dtos/message.dto.ts index e4bb05c..1ae2479 100644 --- a/src/dtos/message.dto.ts +++ b/src/dtos/message.dto.ts @@ -2,4 +2,6 @@ export interface MessageDTO { id: string; content: string; receiverId: string; + emitterId: string; + sendAt: string; } \ No newline at end of file diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index d5b41b5..575f109 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -7,6 +7,7 @@ import Message from "../types/message"; import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; import { MessageDTO } from "../dtos/message.dto"; +import { dateFormater } from "../utils/dateFormater"; export default function ChatPage() { @@ -18,7 +19,7 @@ export default function ChatPage() { const { receiverId } = useParams(); - const { messages, setMessages, addMessage } = useMessageStore(); + const { messages, setMessages, addMessage, updateLastMessage } = useMessageStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { @@ -28,9 +29,18 @@ export default function ChatPage() { const onSubmit: SubmitHandler = async (input) => { if (!receiverId) return; - const message: MessageDTO = { id: crypto.randomUUID(), content: input.content, receiverId: receiverId }; + const message: MessageDTO = {id: "", content: input.content, receiverId: receiverId, emitterId: "", sendAt: (new Date()).toISOString()}; addMessage(message); - await sendMessage(message); + + try { + await sendMessage(message); + } + catch (error) { + navigate('/'); + } + + const messages = await fetchMessages(receiverId); + setMessages(messages); reset(); } @@ -65,7 +75,9 @@ export default function ChatPage() {
{messages.map((message, index) => ( -
{message.content}
+
+ {`${message.content} : ${dateFormater(message.sendAt)}`} +
))}
diff --git a/src/services/message.service.ts b/src/services/message.service.ts index 49a77d0..9e6c9dd 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -2,7 +2,8 @@ import Message from "../types/message"; import { MessageDTO } from "../dtos/message.dto"; export const sendMessage = async (message: MessageDTO): Promise => { - await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`, { + const messageId= crypto.randomUUID() + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -13,6 +14,10 @@ export const sendMessage = async (message: MessageDTO): Promise => { content: message.content, }), }) + //TODO: Gérer plusieurs erreurs dont erreur connexion + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } } export const fetchMessages = async (receiverId: string): Promise => { @@ -26,7 +31,6 @@ export const fetchMessages = async (receiverId: string): Promise => { if (!response.ok) { throw new Error(`Error ${response.status}: ${response.statusText}`); } - //TODO : Attention pas d'erreur si utilisateur n'existe pas ou pas ami return await response.json() } diff --git a/src/stores/message.store.ts b/src/stores/message.store.ts index 8d843c9..a68251d 100644 --- a/src/stores/message.store.ts +++ b/src/stores/message.store.ts @@ -6,10 +6,17 @@ type MessageStore = { messages: Message[]; setMessages: (messages: Message[]) => void; addMessage: (message: Message) => void; + updateLastMessage: (newMessage: Message) => void; } export const useMessageStore = create((set) => ({ messages: [], setMessages: (messages: Message[]) => set({ messages }), addMessage: (message: Message) => set((state) => ({ messages: [...state.messages, message] })), + updateLastMessage: (newMessage: Message) => + set((state) => { + const messages = [...state.messages]; + messages[messages.length - 1] = newMessage; + return { messages }; + }), })) \ No newline at end of file diff --git a/src/types/message.ts b/src/types/message.ts index fc185d0..700df9a 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,6 +1,6 @@ export default interface Message { id: string, - emitterId?: string, + emitterId: string, content: string, - sentAt?: Date, + sendAt: string, } \ No newline at end of file From 51e23f8424f6b87f70f65eb1dc292bfa517b75f2 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 13:39:48 +0100 Subject: [PATCH 061/118] feat : user store, clear user and friends store at user Logout --- .../guards/procteded-route.guard.tsx | 19 ++++++++++++++----- src/pages/login.page.tsx | 8 +++++++- src/services/auth.service.ts | 15 +++++++++++++++ src/stores/friend.store.ts | 2 ++ src/stores/user.store.ts | 11 ++++++----- src/types/user.ts | 4 ++++ 6 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 src/types/user.ts diff --git a/src/components/guards/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx index 1279d36..6b639f3 100644 --- a/src/components/guards/procteded-route.guard.tsx +++ b/src/components/guards/procteded-route.guard.tsx @@ -1,19 +1,28 @@ import { Outlet, useNavigate } from "react-router-dom"; import { useEffect } from "react"; -import { checkUserAuth } from "../../services/auth.service"; +import { checkUserAuth, getCurrentUser } from "../../services/auth.service"; +import { useUserStore } from "../../stores/user.store"; +import { useFriendStore } from "../../stores/friend.store"; export default function ProtectedRoute() { const navigate = useNavigate(); - + const { id, updateUser, clearUser } = useUserStore(); + const { clearFriends } = useFriendStore(); const checkAuth = async () => { const isAuth = await checkUserAuth(); if (!isAuth) { + clearUser(); + clearFriends(); navigate("/login"); } + + if (!id) { + const currentUser = await getCurrentUser(); + updateUser(currentUser); + } }; - useEffect(() => { - checkAuth(); - }, []); + checkAuth(); + return ; } diff --git a/src/pages/login.page.tsx b/src/pages/login.page.tsx index d60ff3e..1ef3c03 100644 --- a/src/pages/login.page.tsx +++ b/src/pages/login.page.tsx @@ -1,13 +1,19 @@ import { useNavigate } from "react-router-dom"; -import { loginUser } from "../services/auth.service"; +import { getCurrentUser, loginUser } from "../services/auth.service"; import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../dtos/user.dto"; +import { useUserStore } from "../stores/user.store"; export default function LoginPage() { const navigate = useNavigate(); + const { updateUser } = useUserStore(); const onSubmit = async (data: UserDTO) => { await loginUser(data); + + const currentUser = await getCurrentUser(); + updateUser(currentUser); + navigate("/"); }; return ( diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index a31bca7..9707f76 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -1,4 +1,5 @@ import { UserDTO } from "../dtos/user.dto"; +import { User } from "../types/user"; export async function loginUser(data: UserDTO) { const response = await fetch( @@ -50,6 +51,20 @@ export async function logoutUser() { } } +export async function getCurrentUser(): Promise { + const response = await fetch( + `${process.env.REACT_APP_API_BASE_URL}/auth/me`, + { + credentials: "include", + } + ); + if (!response.ok) { + const { message } = await response.json(); + throw new Error(message); + } + return response.json(); +} + export async function checkUserAuth() { const response = await fetch( `${process.env.REACT_APP_API_BASE_URL}/auth/me`, diff --git a/src/stores/friend.store.ts b/src/stores/friend.store.ts index f694404..f118f32 100644 --- a/src/stores/friend.store.ts +++ b/src/stores/friend.store.ts @@ -8,6 +8,7 @@ type States = { type Actions = { setFriends: (friends: Friend[]) => void; addFriend: (friend: Friend) => void; + clearFriends: () => void; }; export const useFriendStore = create((set) => ({ @@ -15,4 +16,5 @@ export const useFriendStore = create((set) => ({ setFriends: (friends: Friend[]) => set({ friends }), addFriend: (friend: Friend) => set((state) => ({ friends: [...state.friends, friend] })), + clearFriends: () => set({ friends: [] }), })); diff --git a/src/stores/user.store.ts b/src/stores/user.store.ts index 3792cac..fd2bef4 100644 --- a/src/stores/user.store.ts +++ b/src/stores/user.store.ts @@ -1,17 +1,18 @@ import { create } from "zustand"; +import { User } from "../types/user"; type State = { username: string; - userId: string; + id: string; }; type Action = { clearUser: () => void; - updateUser: (newUser: State) => void; + updateUser: (newUser: User) => void; }; export const useUserStore = create((set) => ({ username: "", - userId: "", - clearUser: () => set({ username: "", userId: "" }), - updateUser: (newUser) => set(newUser), + id: "", + clearUser: () => set({ username: "", id: "" }), + updateUser: (newUser) => set({ username: newUser.username, id: newUser.id }), })); diff --git a/src/types/user.ts b/src/types/user.ts new file mode 100644 index 0000000..769bd33 --- /dev/null +++ b/src/types/user.ts @@ -0,0 +1,4 @@ +export interface User { + username: string; + id: string; +} From 9ba9934ca84276b81af93e43cc172faf7f72d4c1 Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 14:00:15 +0100 Subject: [PATCH 062/118] feat: different color if sender or receiver --- src/dtos/message.dto.ts | 7 ------- src/pages/chat.page.tsx | 18 +++++++++++------- src/services/message.service.ts | 3 +-- src/types/message.ts | 9 +++++---- 4 files changed, 17 insertions(+), 20 deletions(-) delete mode 100644 src/dtos/message.dto.ts diff --git a/src/dtos/message.dto.ts b/src/dtos/message.dto.ts deleted file mode 100644 index 1ae2479..0000000 --- a/src/dtos/message.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface MessageDTO { - id: string; - content: string; - receiverId: string; - emitterId: string; - sendAt: string; -} \ No newline at end of file diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index 575f109..b2dc8e0 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -6,7 +6,6 @@ import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; -import { MessageDTO } from "../dtos/message.dto"; import { dateFormater } from "../utils/dateFormater"; export default function ChatPage() { @@ -29,7 +28,7 @@ export default function ChatPage() { const onSubmit: SubmitHandler = async (input) => { if (!receiverId) return; - const message: MessageDTO = {id: "", content: input.content, receiverId: receiverId, emitterId: "", sendAt: (new Date()).toISOString()}; + const message: Message = {id: "", content: input.content, receiverId: receiverId, emitterId: "", sendAt: (new Date()).toISOString()}; addMessage(message); try { @@ -71,16 +70,21 @@ export default function ChatPage() {
-

Chat Page: {receiverId}

-
{messages.map((message, index) => ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
+ message.receiverId === receiverId ? ( +
+ {`${message.content} : ${dateFormater(message.sendAt)}`} +
+ ) : ( +
+ {`${message.content} : ${dateFormater(message.sendAt)}`} +
+ ) ))}
+
diff --git a/src/services/message.service.ts b/src/services/message.service.ts index 9e6c9dd..4540e3d 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -1,7 +1,6 @@ import Message from "../types/message"; -import { MessageDTO } from "../dtos/message.dto"; -export const sendMessage = async (message: MessageDTO): Promise => { +export const sendMessage = async (message: Message): Promise => { const messageId= crypto.randomUUID() const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { method: 'POST', diff --git a/src/types/message.ts b/src/types/message.ts index 700df9a..97187b8 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -1,6 +1,7 @@ export default interface Message { - id: string, - emitterId: string, - content: string, - sendAt: string, + id: string; + emitterId: string; + receiverId: string; + content: string; + sendAt: string; } \ No newline at end of file From e264cd41753ad8e6ae807cc4e7634258d32b31c6 Mon Sep 17 00:00:00 2001 From: Val Date: Wed, 6 Nov 2024 14:00:47 +0100 Subject: [PATCH 063/118] fix : clear user data at logout --- src/components/guards/procteded-route.guard.tsx | 2 +- src/components/navigation.tsx | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/guards/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx index 6b639f3..e2de0d1 100644 --- a/src/components/guards/procteded-route.guard.tsx +++ b/src/components/guards/procteded-route.guard.tsx @@ -16,7 +16,7 @@ export default function ProtectedRoute() { navigate("/login"); } - if (!id) { + if (!id && isAuth) { const currentUser = await getCurrentUser(); updateUser(currentUser); } diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 20f15c0..db96eb2 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -1,14 +1,23 @@ import * as React from "react"; import { Link } from "react-router-dom"; import { logoutUser } from "../services/auth.service"; +import { useFriendStore } from "../stores/friend.store"; +import { useUserStore } from "../stores/user.store"; export default function Navigation() { + const { clearUser } = useUserStore(); + const { clearFriends } = useFriendStore(); + const handleLogout = () => { + clearUser(); + clearFriends(); + logoutUser(); + }; return (
Chats Friends Settings - + Logout
From 1115f76172ebea822de9e000554a483aa9fd8bf0 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 14:06:19 +0100 Subject: [PATCH 064/118] :sparkles: create a add-friend component for friend page & send friend request avaible --- src/components/add-friend.component.tsx | 62 +++++++++++++++++++++++++ src/pages/friend.page.tsx | 17 +++---- src/services/friend-request.service.ts | 12 +++++ 3 files changed, 83 insertions(+), 8 deletions(-) create mode 100644 src/components/add-friend.component.tsx diff --git a/src/components/add-friend.component.tsx b/src/components/add-friend.component.tsx new file mode 100644 index 0000000..1728f0c --- /dev/null +++ b/src/components/add-friend.component.tsx @@ -0,0 +1,62 @@ +import { useEffect } from "react"; +import { useUserStore } from "../stores/user.store"; +import { SubmitHandler, useForm } from "react-hook-form"; +import { sendFriendRequest } from "../services/friend-request.service"; + +type FormInputs = { + content: string; +} + +export default function AddFriend() { + + const idTemp = "7f1c5ce0-a094-4ae3-b03a-669db1770eed" + + const { username, userId, clearUser, updateUser } = useUserStore(); + + const { register, handleSubmit, reset, formState: { errors } } = useForm({ + defaultValues: { + content: "" + } + }) + + useEffect(() => { + console.log(username) + console.log(userId) + }, []); + + const onSubmit: SubmitHandler = async (input) => { + console.log("me", idTemp); + console.log("to", input.content); + + await sendFriendRequest(input.content); + + reset(); + } + + return ( +
+ Add a friend + +
+ Your ID: + {idTemp} + +
+ +
+ Enter a friend ID: + + + + + +
+ +
+ ); +} \ No newline at end of file diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index 7bad046..2351966 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -3,6 +3,7 @@ import FriendRequestCard from "../components/friend-request-card.component"; import { eventFetchFriendRequests, fetchFriendRequests } from "../services/friend-request.service"; import Loader from "../components/loaders/loader.component"; import { FriendRequest } from "../types/friend-request"; +import AddFriend from "../components/add-friend.component"; export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); @@ -10,7 +11,6 @@ export default function FriendPage() { useEffect(() => { loadInitialRequests(); - const handleNewFriendRequest = (data: FriendRequest) => { setFriendRequests(prevRequests => {return [data, ...prevRequests];}); }; @@ -37,15 +37,16 @@ export default function FriendPage() {
{loading ? : null} -

Friend Page

-
- Friends Requests - - {friendRequests.map((request) => ( - - ))} + +
+ Friends Requests + + {friendRequests.map((request) => ( + + ))} +
diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 0761943..6a5a409 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -22,4 +22,16 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest eventSource.close(); }; return eventSource; +} + +export const sendFriendRequest = async (receiverId: string) => { + const randomuuid = crypto.randomUUID(); + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ receiverId: receiverId }), + }) } \ No newline at end of file From 9fee1ed9a71614d443cb46a8c9696c0651e1b95d Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 14:13:43 +0100 Subject: [PATCH 065/118] :sparkles: accept friend now avaible --- src/components/friend-request-card.component.tsx | 10 +++++++++- src/services/friend-request.service.ts | 10 ++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index e2107b6..81e0ace 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -1,7 +1,13 @@ +import { acceptRequest } from "../services/friend-request.service"; import { FriendRequest } from "../types/friend-request"; import { dateFormater } from "../utils/dateFormater"; export default function FriendRequestCard(request: FriendRequest) { + + function acceptFriendRequest() { + acceptRequest(request.id.toString()); + } + return (
@@ -11,7 +17,9 @@ export default function FriendRequestCard(request: FriendRequest) { {dateFormater(request.requestedAt)}
- +
diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 6a5a409..1e6bbc5 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -34,4 +34,14 @@ export const sendFriendRequest = async (receiverId: string) => { credentials: 'include', body: JSON.stringify({ receiverId: receiverId }), }) +} + +export const acceptRequest = async (requestId: string) => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${requestId}/accept`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) } \ No newline at end of file From 07d668b89a0a9ea6c1b6477ea00df70b5066d016 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 14:30:27 +0100 Subject: [PATCH 066/118] :sparkles: reload page --- src/components/friend-request-card.component.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index 81e0ace..e949ac7 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -4,8 +4,10 @@ import { dateFormater } from "../utils/dateFormater"; export default function FriendRequestCard(request: FriendRequest) { - function acceptFriendRequest() { - acceptRequest(request.id.toString()); + async function acceptFriendRequest() { + await acceptRequest(request.id.toString()); + + window.location.reload(); } return ( From 634fce7f50c60042735295b3554286cd5e5181b0 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 14:31:13 +0100 Subject: [PATCH 067/118] :bug: bug fix --- src/components/add-friend.component.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/add-friend.component.tsx b/src/components/add-friend.component.tsx index 1728f0c..b472516 100644 --- a/src/components/add-friend.component.tsx +++ b/src/components/add-friend.component.tsx @@ -11,7 +11,7 @@ export default function AddFriend() { const idTemp = "7f1c5ce0-a094-4ae3-b03a-669db1770eed" - const { username, userId, clearUser, updateUser } = useUserStore(); + const { username, clearUser, updateUser } = useUserStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { @@ -21,7 +21,6 @@ export default function AddFriend() { useEffect(() => { console.log(username) - console.log(userId) }, []); const onSubmit: SubmitHandler = async (input) => { From fd2671ee3a27c48fbac59cf8fb174629a8e2d1cc Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 14:42:57 +0100 Subject: [PATCH 068/118] :sparkles: true id used in friend page --- src/components/add-friend.component.tsx | 16 +++------------- src/components/guards/procteded-route.guard.tsx | 1 - src/pages/chat-list.page.tsx | 1 - 3 files changed, 3 insertions(+), 15 deletions(-) diff --git a/src/components/add-friend.component.tsx b/src/components/add-friend.component.tsx index b472516..ebcffa1 100644 --- a/src/components/add-friend.component.tsx +++ b/src/components/add-friend.component.tsx @@ -9,26 +9,16 @@ type FormInputs = { export default function AddFriend() { - const idTemp = "7f1c5ce0-a094-4ae3-b03a-669db1770eed" - - const { username, clearUser, updateUser } = useUserStore(); + const user = useUserStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { content: "" } }) - - useEffect(() => { - console.log(username) - }, []); const onSubmit: SubmitHandler = async (input) => { - console.log("me", idTemp); - console.log("to", input.content); - await sendFriendRequest(input.content); - reset(); } @@ -38,10 +28,10 @@ export default function AddFriend() {
Your ID: - {idTemp} + {user.id}
diff --git a/src/components/guards/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx index e2de0d1..339d81c 100644 --- a/src/components/guards/procteded-route.guard.tsx +++ b/src/components/guards/procteded-route.guard.tsx @@ -1,5 +1,4 @@ import { Outlet, useNavigate } from "react-router-dom"; -import { useEffect } from "react"; import { checkUserAuth, getCurrentUser } from "../../services/auth.service"; import { useUserStore } from "../../stores/user.store"; import { useFriendStore } from "../../stores/friend.store"; diff --git a/src/pages/chat-list.page.tsx b/src/pages/chat-list.page.tsx index ad83614..7156492 100644 --- a/src/pages/chat-list.page.tsx +++ b/src/pages/chat-list.page.tsx @@ -7,7 +7,6 @@ export default function ChatListPage() { const [chats, setChats] = useState([]); useEffect(() => { - console.log(data); setChats(data); }, []); From 31658a2ae34cd4f7caa1a92c43868e4de1d08999 Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 14:49:26 +0100 Subject: [PATCH 069/118] feat: add message service adapter --- src/adapters/message.adapter.ts | 7 ++ src/pages/chat.page.tsx | 13 ++-- src/services/message.service.ts | 117 +++++++++++++++++++++----------- 3 files changed, 92 insertions(+), 45 deletions(-) create mode 100644 src/adapters/message.adapter.ts diff --git a/src/adapters/message.adapter.ts b/src/adapters/message.adapter.ts new file mode 100644 index 0000000..bd66f6f --- /dev/null +++ b/src/adapters/message.adapter.ts @@ -0,0 +1,7 @@ +import Message from "../types/message"; + +export interface MessageAdapter { + sendMessage: (message: Message) => Promise; + fetchMessages: (receiverId: string) => Promise; + eventFetchMessages: (onMessageReceived: (data: Message) => void) => EventSource; +} \ No newline at end of file diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index b2dc8e0..64f72d7 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -4,9 +4,12 @@ import { useEffect } from "react"; import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; -import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; +import { FalseMessageService, MessageService } from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; import { dateFormater } from "../utils/dateFormater"; +import { MessageAdapter } from "../adapters/message.adapter"; + +const messageService : MessageAdapter = new FalseMessageService(); export default function ChatPage() { @@ -32,13 +35,13 @@ export default function ChatPage() { addMessage(message); try { - await sendMessage(message); + await messageService.sendMessage(message); } catch (error) { navigate('/'); } - const messages = await fetchMessages(receiverId); + const messages = await messageService.fetchMessages(receiverId); setMessages(messages); reset(); } @@ -47,7 +50,7 @@ export default function ChatPage() { if (!receiverId) return; const loadMessages = async (): Promise => { try { - const messages = await fetchMessages(receiverId); + const messages = await messageService.fetchMessages(receiverId); setMessages(messages); } catch (error) { navigate('/chats'); @@ -59,7 +62,7 @@ export default function ChatPage() { addMessage(message); }; - const eventSource = eventFetchMessages(handleNewMessage); + const eventSource = messageService.eventFetchMessages(handleNewMessage); return () => { eventSource.close(); diff --git a/src/services/message.service.ts b/src/services/message.service.ts index 4540e3d..ef77c0d 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -1,47 +1,84 @@ import Message from "../types/message"; +import { MessageAdapter } from "../adapters/message.adapter"; -export const sendMessage = async (message: Message): Promise => { - const messageId= crypto.randomUUID() - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ - receiverId: message.receiverId, - content: message.content, - }), - }) - //TODO: Gérer plusieurs erreurs dont erreur connexion - if (!response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); +export class MessageService implements MessageAdapter { + sendMessage = async (message: Message): Promise => { + const messageId= crypto.randomUUID() + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + receiverId: message.receiverId, + content: message.content, + }), + }) + //TODO: Gérer plusieurs erreurs dont erreur connexion + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + } + + fetchMessages = async (receiverId: string): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + return await response.json() } -} -export const fetchMessages = async (receiverId: string): Promise => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - }) - if (!response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); + eventFetchMessages = (onMessageReceived: (data: Message) => void) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener('message-received', (event) => { + const data = JSON.parse(event.data); + onMessageReceived(data); + }) + + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; } - return await response.json() } -export const eventFetchMessages = (onMessageReceived: (data: Message) => void) => { - const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) - eventSource.addEventListener('message-received', (event) => { - const data = JSON.parse(event.data); - onMessageReceived(data); - }) - - eventSource.onerror = (error) => { - eventSource.close(); - }; - return eventSource; -} \ No newline at end of file + +export class FalseMessageService implements MessageAdapter { + sendMessage(message: Message): Promise { + throw new Error("Method not implemented."); + } + + fetchMessages = async (receiverId: string): Promise => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/messages/${receiverId}/`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + return await response.json() + } + + eventFetchMessages = (onMessageReceived: (data: Message) => void) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener('message-received', (event) => { + const data = JSON.parse(event.data); + onMessageReceived(data); + }) + + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; + } +} From 677467e6c8c64444981b51e66780d3fdf56d9c83 Mon Sep 17 00:00:00 2001 From: Mathis Zerari <97899958+mathiszerari@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:53:51 +0100 Subject: [PATCH 070/118] :fire: remove settings from navbar (#16) --- src/components/navigation.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index db96eb2..f9a3bef 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -16,7 +16,6 @@ export default function Navigation() {
Chats Friends - Settings Logout From b5b8df1b17ff8bbc267b1ecc665e734e31c2a7e8 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 16:16:32 +0100 Subject: [PATCH 071/118] :sparkles: new event listener for accepted notif --- src/services/friend-request.service.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 1e6bbc5..372843b 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -24,6 +24,19 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest return eventSource; } +export const eventAcceptedFriendRequest = (onRequestAccepted: (data: FriendRequest) => void) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener('friend-request-accepted', (event) => { + const data = JSON.parse(event.data); + onRequestAccepted(data); + }) + + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; +} + export const sendFriendRequest = async (receiverId: string) => { const randomuuid = crypto.randomUUID(); const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { From 909e052b7e492e3b3961ad4f8ecd509e502aa939 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 16:16:43 +0100 Subject: [PATCH 072/118] :sparkles: new notif page --- src/pages/notification-list.page.tsx | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/pages/notification-list.page.tsx diff --git a/src/pages/notification-list.page.tsx b/src/pages/notification-list.page.tsx new file mode 100644 index 0000000..64a0b93 --- /dev/null +++ b/src/pages/notification-list.page.tsx @@ -0,0 +1,9 @@ +import { useNotificationStore } from "../stores/notification.store"; + +export default function NotificationListPage() { + const { notifications, addNotification } = useNotificationStore(); + + return ( +
NotificationListPage
+ ); +} \ No newline at end of file From 063fe6e571ad187b9282ae9c898568b1419281d4 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 16:16:59 +0100 Subject: [PATCH 073/118] :sparkles: new notif type and store --- src/stores/notification.store.ts | 13 +++++++++++++ src/types/notification.ts | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 src/stores/notification.store.ts create mode 100644 src/types/notification.ts diff --git a/src/stores/notification.store.ts b/src/stores/notification.store.ts new file mode 100644 index 0000000..0472287 --- /dev/null +++ b/src/stores/notification.store.ts @@ -0,0 +1,13 @@ +import { create } from "zustand/react"; + +import Notification from "../types/notification"; + +type NotificationsStore = { + notifications: Notification[]; + addNotification: (notification: Notification) => void; +} + +export const useNotificationStore = create((set) => ({ + notifications: [], + addNotification: (notification: Notification) => set((state) => ({ notifications: [notification, ...state.notifications] })), +})) \ No newline at end of file diff --git a/src/types/notification.ts b/src/types/notification.ts new file mode 100644 index 0000000..f8f9626 --- /dev/null +++ b/src/types/notification.ts @@ -0,0 +1,7 @@ +export default interface Notification { + id: string; + type: string; + emitterId?: string; + content?: string; + receivedAt: string; +} \ No newline at end of file From f6edf03a42aad9f2a9dfc436a3b675fda92fbe5a Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 16:39:35 +0100 Subject: [PATCH 074/118] :construction: try to save in the notification store friend-request-received from App.tsx --- src/App.tsx | 31 ++++++++++++++++++++++++++ src/pages/friend.page.tsx | 9 -------- src/pages/notification-list.page.tsx | 3 --- src/services/friend-request.service.ts | 2 +- 4 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index b4af07e..904274c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,34 @@ import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; +import { useEffect } from "react"; +import { eventFetchFriendRequests } from "./services/friend-request.service"; +import { useNotificationStore } from "./stores/notification.store"; export default function App() { + + const { notifications, addNotification } = useNotificationStore(); + + useEffect(() => { + const handleNewFriendRequest = (data: { userId: string }) => { + setFriendRequests(prevRequests => { return [data, ...prevRequests]; }); + const notification = { + id: crypto.randomUUID(), + type: "friend-request-received", + emitterId: data.userId, + content: 'A user asked you in friend', + receivedAt: new Date().toISOString(), + } + console.log(data); + addNotification(notification); + }; + + const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + + return () => { + eventSource.close(); + }; + }) + return (
@@ -9,3 +36,7 @@ export default function App() {
); } +function setFriendRequests(arg0: (prevRequests: any) => any[]) { + throw new Error("Function not implemented."); +} + diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index 2351966..a9378f5 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -11,15 +11,6 @@ export default function FriendPage() { useEffect(() => { loadInitialRequests(); - const handleNewFriendRequest = (data: FriendRequest) => { - setFriendRequests(prevRequests => {return [data, ...prevRequests];}); - }; - - const eventSource = eventFetchFriendRequests(handleNewFriendRequest); - - return () => { - eventSource.close(); - }; }, []); async function loadInitialRequests() { diff --git a/src/pages/notification-list.page.tsx b/src/pages/notification-list.page.tsx index 64a0b93..2d17d97 100644 --- a/src/pages/notification-list.page.tsx +++ b/src/pages/notification-list.page.tsx @@ -1,7 +1,4 @@ -import { useNotificationStore } from "../stores/notification.store"; - export default function NotificationListPage() { - const { notifications, addNotification } = useNotificationStore(); return (
NotificationListPage
diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 372843b..bb77692 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -11,7 +11,7 @@ export async function fetchFriendRequests() { })); } -export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest) => void) => { +export const eventFetchFriendRequests = (onRequestReceived: (data: {userId: string}) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-received', (event) => { const data = JSON.parse(event.data); From 174ff76cf6cae8b298ccda1997bfa7d5f09aac8d Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 16:48:18 +0100 Subject: [PATCH 075/118] :construction: store the accepted friend requests --- src/App.tsx | 27 +++++++++++++++++++++++-- src/dtos/friend-request-accepted.dto.ts | 5 +++++ src/dtos/friend-request-received.dto.ts | 3 +++ src/services/friend-request.service.ts | 3 ++- 4 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 src/dtos/friend-request-accepted.dto.ts create mode 100644 src/dtos/friend-request-received.dto.ts diff --git a/src/App.tsx b/src/App.tsx index 904274c..a65e7ab 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,15 +1,17 @@ import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; import { useEffect } from "react"; -import { eventFetchFriendRequests } from "./services/friend-request.service"; +import { eventAcceptedFriendRequest, eventFetchFriendRequests } from "./services/friend-request.service"; import { useNotificationStore } from "./stores/notification.store"; +import { FriendRequestReceivedDTO } from "./dtos/friend-request-received.dto"; +import { FriendRequestAcceptedDTO } from "./dtos/friend-request-accepted.dto"; export default function App() { const { notifications, addNotification } = useNotificationStore(); useEffect(() => { - const handleNewFriendRequest = (data: { userId: string }) => { + const handleNewFriendRequest = (data: FriendRequestReceivedDTO ) => { setFriendRequests(prevRequests => { return [data, ...prevRequests]; }); const notification = { id: crypto.randomUUID(), @@ -29,6 +31,27 @@ export default function App() { }; }) + useEffect(() => { + const handleFriendRequestAccepted = (data: FriendRequestAcceptedDTO) => { + setFriendRequests(prevRequests => { return [data, ...prevRequests]; }); + const notification = { + id: data.id, + type: "friend-request-accepted", + emitterId: data.senderId, + content: 'A user asked you in friend', + receivedAt: data.requestedAt, + } + console.log(data); + addNotification(notification); + }; + + const eventSource = eventAcceptedFriendRequest(handleFriendRequestAccepted); + + return () => { + eventSource.close(); + }; + }) + return (
diff --git a/src/dtos/friend-request-accepted.dto.ts b/src/dtos/friend-request-accepted.dto.ts new file mode 100644 index 0000000..012dbf7 --- /dev/null +++ b/src/dtos/friend-request-accepted.dto.ts @@ -0,0 +1,5 @@ +export interface FriendRequestAcceptedDTO { + id: string, + senderId: string, + requestedAt: string +} diff --git a/src/dtos/friend-request-received.dto.ts b/src/dtos/friend-request-received.dto.ts new file mode 100644 index 0000000..de5f94a --- /dev/null +++ b/src/dtos/friend-request-received.dto.ts @@ -0,0 +1,3 @@ +export interface FriendRequestReceivedDTO { + userId: string +} diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index bb77692..97a7650 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -1,3 +1,4 @@ +import { FriendRequestAcceptedDTO } from "../dtos/friend-request-accepted.dto"; import { FriendRequest } from "../types/friend-request"; export async function fetchFriendRequests() { @@ -24,7 +25,7 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: {userId: stri return eventSource; } -export const eventAcceptedFriendRequest = (onRequestAccepted: (data: FriendRequest) => void) => { +export const eventAcceptedFriendRequest = (onRequestAccepted: (data: FriendRequestAcceptedDTO) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-accepted', (event) => { const data = JSON.parse(event.data); From eae470c729596d8c360b167d0bd8929a0a7bb09f Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 16:49:49 +0100 Subject: [PATCH 076/118] feat: add loader loading messages --- src/common/error.common.ts | 23 ++++++ src/components/loaders/messages.loader.tsx | 30 +++----- src/components/navigation.tsx | 2 +- src/errors/bad-request.error.ts | 8 ++ src/index.tsx | 2 + src/pages/chat.page.tsx | 86 +++++++++++++--------- src/services/message.service.ts | 13 +++- src/stores/message.store.ts | 6 +- src/types/message.ts | 1 + 9 files changed, 109 insertions(+), 62 deletions(-) create mode 100644 src/common/error.common.ts create mode 100644 src/errors/bad-request.error.ts diff --git a/src/common/error.common.ts b/src/common/error.common.ts new file mode 100644 index 0000000..0a8ce42 --- /dev/null +++ b/src/common/error.common.ts @@ -0,0 +1,23 @@ +export class CommonError extends Error { + public statusCode: number; + public errorCode: string; + public details?: any; + + constructor(statusCode: number, errorCode: string, message: string, details?: any) { + super(message); + this.statusCode = statusCode; + this.errorCode = errorCode; + this.details = details; + + Object.setPrototypeOf(this, CommonError.prototype); + } + + toJSON() { + return { + statusCode: this.statusCode, + errorCode: this.errorCode, + message: this.message, + details: this.details || null, + }; + } +} diff --git a/src/components/loaders/messages.loader.tsx b/src/components/loaders/messages.loader.tsx index 8c5f891..8dcaf49 100644 --- a/src/components/loaders/messages.loader.tsx +++ b/src/components/loaders/messages.loader.tsx @@ -1,19 +1,13 @@ -import { ReactNode, useEffect } from "react"; -import { useNavigate } from "react-router-dom"; - -type LoaderProps = { - receiverId?: string; - children: ReactNode; +import { redirect } from "react-router-dom"; +import { MessageService } from "../../services/message.service"; + +export const MessagesLoader = async ({ params }: { params: Record }) => { + const messageService = new MessageService(); + try { + const { receiverId } = params; + if (!receiverId) throw new Error("Receiver ID missing"); + return await messageService.fetchMessages(receiverId); + } catch (error) { + return redirect('/'); + } }; - -export default function MessagesLoader({ receiverId, children }: LoaderProps) { - const navigate = useNavigate(); - - useEffect(() => { - if (!receiverId) { - navigate('/'); - } - }, [receiverId, navigate]); - - return <>{children}; -} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index db96eb2..37c2ca1 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -14,7 +14,7 @@ export default function Navigation() { }; return (
- Chats + Chats Friends Settings diff --git a/src/errors/bad-request.error.ts b/src/errors/bad-request.error.ts new file mode 100644 index 0000000..51647ba --- /dev/null +++ b/src/errors/bad-request.error.ts @@ -0,0 +1,8 @@ +import { CommonError } from "../common/error.common"; + +export class BadRequestError extends CommonError { + constructor(message: string) { + super(400, 'BAD_REQUEST', message); + Object.setPrototypeOf(this, BadRequestError.prototype); + } +} diff --git a/src/index.tsx b/src/index.tsx index 34157e9..01d37d5 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,6 +13,7 @@ import ChatPage from "./pages/chat.page"; import FriendPage from "./pages/friend.page"; import ProtectedRoute from "./components/guards/procteded-route.guard"; import GuestRoute from "./components/guards/guest-route.guard"; +import { MessagesLoader } from "./components/loaders/messages.loader"; const router = createBrowserRouter([ { @@ -33,6 +34,7 @@ const router = createBrowserRouter([ { path: "/chats/:receiverId", element: , + loader: MessagesLoader, }, { path: "/friends", diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index 64f72d7..25a4285 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -1,15 +1,17 @@ -import {useNavigate, useParams} from "react-router-dom"; +import { useLoaderData, useNavigate, useParams } from "react-router-dom"; import { SubmitHandler, useForm } from "react-hook-form"; import { useEffect } from "react"; import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; import { FalseMessageService, MessageService } from "../services/message.service"; -import MessagesLoader from "../components/loaders/messages.loader"; import { dateFormater } from "../utils/dateFormater"; import { MessageAdapter } from "../adapters/message.adapter"; +import { BadRequestError } from "../errors/bad-request.error"; +import { useUserStore } from "../stores/user.store"; -const messageService : MessageAdapter = new FalseMessageService(); +// const messageService : MessageAdapter = new FalseMessageService(); +const messageService: MessageAdapter = new MessageService(); export default function ChatPage() { @@ -17,11 +19,14 @@ export default function ChatPage() { content: string; } + const initialMessages = useLoaderData() as Message[]; + const navigate = useNavigate(); const { receiverId } = useParams(); - const { messages, setMessages, addMessage, updateLastMessage } = useMessageStore(); + const { messages, setMessages, addMessage, setErrorLastMessage } = useMessageStore(); + const { id } = useUserStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { @@ -29,34 +34,40 @@ export default function ChatPage() { } }) - const onSubmit: SubmitHandler = async (input) => { - if (!receiverId) return; - const message: Message = {id: "", content: input.content, receiverId: receiverId, emitterId: "", sendAt: (new Date()).toISOString()}; - addMessage(message); - + const sendMessage = async (message: Message) => { try { await messageService.sendMessage(message); + } catch (error) { + if (error instanceof BadRequestError) { + navigate('/'); + } + else { + setErrorLastMessage(); + } } - catch (error) { - navigate('/'); - } + } - const messages = await messageService.fetchMessages(receiverId); - setMessages(messages); + const onSubmit: SubmitHandler = async (input) => { + const messageId = crypto.randomUUID() + if (!receiverId) return; + const message: Message = { + id: messageId, + content: input.content, + receiverId: receiverId, + emitterId: id, + sendAt: (new Date()).toISOString() + }; + addMessage(message); + await sendMessage(message); reset(); } + const retryMessage = async (message: Message) => { + //TODO: Implementer la fonctionnalité de retry + } + useEffect(() => { - if (!receiverId) return; - const loadMessages = async (): Promise => { - try { - const messages = await messageService.fetchMessages(receiverId); - setMessages(messages); - } catch (error) { - navigate('/chats'); - } - }; - loadMessages(); + setMessages(initialMessages); const handleNewMessage = (message: Message) => { addMessage(message); @@ -70,20 +81,24 @@ export default function ChatPage() { }, [receiverId]); return ( - -
{messages.map((message, index) => ( - message.receiverId === receiverId ? ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) : ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) + message.error ? ( +
+ {`${message.content} : ${dateFormater(message.sendAt)}`} + +
+ ) : + message.receiverId === receiverId ? ( +
+ {`${message.content} : ${dateFormater(message.sendAt)}`} +
+ ) : ( +
+ {`${message.content} : ${dateFormater(message.sendAt)}`} +
+ ) ))}
@@ -95,6 +110,5 @@ export default function ChatPage() {
-
); } \ No newline at end of file diff --git a/src/services/message.service.ts b/src/services/message.service.ts index ef77c0d..ddf2052 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -1,10 +1,10 @@ import Message from "../types/message"; import { MessageAdapter } from "../adapters/message.adapter"; +import { BadRequestError } from "../errors/bad-request.error"; export class MessageService implements MessageAdapter { sendMessage = async (message: Message): Promise => { - const messageId= crypto.randomUUID() - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${messageId}/send`, { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -16,8 +16,13 @@ export class MessageService implements MessageAdapter { }), }) //TODO: Gérer plusieurs erreurs dont erreur connexion - if (!response.ok) { - throw new Error(`Error ${response.status}: ${response.statusText}`); + if (response.status === 400) { + throw new BadRequestError("You are not friend with this user"); + } + else { + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } } } diff --git a/src/stores/message.store.ts b/src/stores/message.store.ts index a68251d..8328523 100644 --- a/src/stores/message.store.ts +++ b/src/stores/message.store.ts @@ -6,17 +6,17 @@ type MessageStore = { messages: Message[]; setMessages: (messages: Message[]) => void; addMessage: (message: Message) => void; - updateLastMessage: (newMessage: Message) => void; + setErrorLastMessage: () => void; } export const useMessageStore = create((set) => ({ messages: [], setMessages: (messages: Message[]) => set({ messages }), addMessage: (message: Message) => set((state) => ({ messages: [...state.messages, message] })), - updateLastMessage: (newMessage: Message) => + setErrorLastMessage: () => set((state) => { const messages = [...state.messages]; - messages[messages.length - 1] = newMessage; + messages[messages.length - 1] = {...messages[messages.length - 1], error: true}; return { messages }; }), })) \ No newline at end of file diff --git a/src/types/message.ts b/src/types/message.ts index 97187b8..df043e8 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -4,4 +4,5 @@ export default interface Message { receiverId: string; content: string; sendAt: string; + error?: boolean; } \ No newline at end of file From 48007e1f922708003ddcf8b3a9cb6444f464f5df Mon Sep 17 00:00:00 2001 From: Smachy Date: Wed, 6 Nov 2024 17:00:12 +0100 Subject: [PATCH 077/118] feat: retry messages --- src/components/loaders/messages.loader.tsx | 11 +++-------- src/pages/chat.page.tsx | 4 ++-- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/components/loaders/messages.loader.tsx b/src/components/loaders/messages.loader.tsx index 8dcaf49..54ae657 100644 --- a/src/components/loaders/messages.loader.tsx +++ b/src/components/loaders/messages.loader.tsx @@ -1,13 +1,8 @@ -import { redirect } from "react-router-dom"; import { MessageService } from "../../services/message.service"; export const MessagesLoader = async ({ params }: { params: Record }) => { const messageService = new MessageService(); - try { - const { receiverId } = params; - if (!receiverId) throw new Error("Receiver ID missing"); - return await messageService.fetchMessages(receiverId); - } catch (error) { - return redirect('/'); - } + const { receiverId } = params; + if (!receiverId) throw new Error("Receiver ID missing"); + return await messageService.fetchMessages(receiverId); }; diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index 25a4285..a8dc344 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -63,7 +63,7 @@ export default function ChatPage() { } const retryMessage = async (message: Message) => { - //TODO: Implementer la fonctionnalité de retry + console.log(message); } useEffect(() => { @@ -87,7 +87,7 @@ export default function ChatPage() { message.error ? (
{`${message.content} : ${dateFormater(message.sendAt)}`} - +
) : message.receiverId === receiverId ? ( From 1b14c13569112790be0990e305afcf254f24ff8f Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 21:16:46 +0100 Subject: [PATCH 078/118] :construction: event in friend page is working --- src/App.tsx | 64 ++++++------------- src/components/navigation.tsx | 1 + src/index.tsx | 5 ++ src/pages/friend.page.tsx | 10 +++ ...n-list.page.tsx => notifications.page.tsx} | 6 ++ src/services/friend-request.service.ts | 4 +- src/stores/notification.store.ts | 13 ---- src/types/friend-request.ts | 2 +- 8 files changed, 45 insertions(+), 60 deletions(-) rename src/pages/{notification-list.page.tsx => notifications.page.tsx} (50%) delete mode 100644 src/stores/notification.store.ts diff --git a/src/App.tsx b/src/App.tsx index a65e7ab..ab50c31 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,56 +1,36 @@ import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; import { useEffect } from "react"; -import { eventAcceptedFriendRequest, eventFetchFriendRequests } from "./services/friend-request.service"; -import { useNotificationStore } from "./stores/notification.store"; -import { FriendRequestReceivedDTO } from "./dtos/friend-request-received.dto"; -import { FriendRequestAcceptedDTO } from "./dtos/friend-request-accepted.dto"; +import Notification from "./types/notification"; +import { eventFetchFriendRequests } from "./services/friend-request.service"; +import { FriendRequest } from "./types/friend-request"; export default function App() { - const { notifications, addNotification } = useNotificationStore(); + function saveReceivedRequest(request: FriendRequest) { + const notification: Notification = { + id: request.id, + type: "friend-request-received", + emitterId: request.senderId, + content: request.senderId + ' asked you in friend', + receivedAt: request.requestedAt, + } - useEffect(() => { - const handleNewFriendRequest = (data: FriendRequestReceivedDTO ) => { - setFriendRequests(prevRequests => { return [data, ...prevRequests]; }); - const notification = { - id: crypto.randomUUID(), - type: "friend-request-received", - emitterId: data.userId, - content: 'A user asked you in friend', - receivedAt: new Date().toISOString(), - } - console.log(data); - addNotification(notification); - }; - - const eventSource = eventFetchFriendRequests(handleNewFriendRequest); - - return () => { - eventSource.close(); - }; - }) + //TODO stocker les données dans le localStorage + } useEffect(() => { - const handleFriendRequestAccepted = (data: FriendRequestAcceptedDTO) => { - setFriendRequests(prevRequests => { return [data, ...prevRequests]; }); - const notification = { - id: data.id, - type: "friend-request-accepted", - emitterId: data.senderId, - content: 'A user asked you in friend', - receivedAt: data.requestedAt, - } - console.log(data); - addNotification(notification); - }; + const handleNewFriendRequest = (request: any) => () => { + console.log("request", request); + saveReceivedRequest(request); + } - const eventSource = eventAcceptedFriendRequest(handleFriendRequestAccepted); + const eventSource = eventFetchFriendRequests(handleNewFriendRequest); return () => { eventSource.close(); }; - }) + }, []) return (
@@ -58,8 +38,4 @@ export default function App() {
); -} -function setFriendRequests(arg0: (prevRequests: any) => any[]) { - throw new Error("Function not implemented."); -} - +} \ No newline at end of file diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index f9a3bef..8abbfef 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -16,6 +16,7 @@ export default function Navigation() {
Chats Friends + Notifications Logout diff --git a/src/index.tsx b/src/index.tsx index 34157e9..bc09127 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -13,6 +13,7 @@ import ChatPage from "./pages/chat.page"; import FriendPage from "./pages/friend.page"; import ProtectedRoute from "./components/guards/procteded-route.guard"; import GuestRoute from "./components/guards/guest-route.guard"; +import NotificationListPage from "./pages/notifications.page"; const router = createBrowserRouter([ { @@ -38,6 +39,10 @@ const router = createBrowserRouter([ path: "/friends", element: , }, + { + path: "/notifications", + element: , + }, ], }, ], diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index a9378f5..c98e568 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -11,6 +11,16 @@ export default function FriendPage() { useEffect(() => { loadInitialRequests(); + + const handleNewFriendRequest = (data: any) => { + setFriendRequests(prevRequests => {return [data, ...prevRequests];}); + }; + + const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + + return () => { + eventSource.close(); + }; }, []); async function loadInitialRequests() { diff --git a/src/pages/notification-list.page.tsx b/src/pages/notifications.page.tsx similarity index 50% rename from src/pages/notification-list.page.tsx rename to src/pages/notifications.page.tsx index 2d17d97..a0a4792 100644 --- a/src/pages/notification-list.page.tsx +++ b/src/pages/notifications.page.tsx @@ -1,5 +1,11 @@ +import { useEffect } from "react"; + export default function NotificationListPage() { + useEffect(() => { + console.log("notifications"); + }, []); + return (
NotificationListPage
); diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 97a7650..704192f 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -12,13 +12,13 @@ export async function fetchFriendRequests() { })); } -export const eventFetchFriendRequests = (onRequestReceived: (data: {userId: string}) => void) => { +export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-received', (event) => { const data = JSON.parse(event.data); + console.log(data); onRequestReceived(data); }) - eventSource.onerror = (error) => { eventSource.close(); }; diff --git a/src/stores/notification.store.ts b/src/stores/notification.store.ts deleted file mode 100644 index 0472287..0000000 --- a/src/stores/notification.store.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { create } from "zustand/react"; - -import Notification from "../types/notification"; - -type NotificationsStore = { - notifications: Notification[]; - addNotification: (notification: Notification) => void; -} - -export const useNotificationStore = create((set) => ({ - notifications: [], - addNotification: (notification: Notification) => set((state) => ({ notifications: [notification, ...state.notifications] })), -})) \ No newline at end of file diff --git a/src/types/friend-request.ts b/src/types/friend-request.ts index e57db80..fd47801 100644 --- a/src/types/friend-request.ts +++ b/src/types/friend-request.ts @@ -1,5 +1,5 @@ export interface FriendRequest { - id: number; + id: string; senderId: string; requestedAt: string; } \ No newline at end of file From 7fb9a271b4f102c5f66e8942118f3519266cb4bb Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 21:22:13 +0100 Subject: [PATCH 079/118] :construction: receiving friend request is working everywhere --- src/App.tsx | 5 ++- src/services/friend-request.service.ts | 44 +++++++++++++------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index ab50c31..d30b9d2 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,11 +3,10 @@ import Navigation from "./components/navigation"; import { useEffect } from "react"; import Notification from "./types/notification"; import { eventFetchFriendRequests } from "./services/friend-request.service"; -import { FriendRequest } from "./types/friend-request"; export default function App() { - function saveReceivedRequest(request: FriendRequest) { + function saveReceivedRequest(request: any) { const notification: Notification = { id: request.id, type: "friend-request-received", @@ -20,7 +19,7 @@ export default function App() { } useEffect(() => { - const handleNewFriendRequest = (request: any) => () => { + const handleNewFriendRequest = (request: any) => { console.log("request", request); saveReceivedRequest(request); } diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 704192f..fbc8964 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -12,6 +12,28 @@ export async function fetchFriendRequests() { })); } +export const sendFriendRequest = async (receiverId: string) => { + const randomuuid = crypto.randomUUID(); + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ receiverId: receiverId }), + }) +} + +export const acceptRequest = async (requestId: string) => { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${requestId}/accept`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + }) +} + export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-received', (event) => { @@ -36,26 +58,4 @@ export const eventAcceptedFriendRequest = (onRequestAccepted: (data: FriendReque eventSource.close(); }; return eventSource; -} - -export const sendFriendRequest = async (receiverId: string) => { - const randomuuid = crypto.randomUUID(); - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - body: JSON.stringify({ receiverId: receiverId }), - }) -} - -export const acceptRequest = async (requestId: string) => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${requestId}/accept`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - credentials: 'include', - }) } \ No newline at end of file From ac5f10f2b3574f58870dcd9c85cf5189b152a687 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 21:27:01 +0100 Subject: [PATCH 080/118] :sparkles: receiving friend request is working and well --- src/App.tsx | 3 ++- src/services/friend-request.service.ts | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index d30b9d2..69d2acd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -15,12 +15,13 @@ export default function App() { receivedAt: request.requestedAt, } + console.log(notification); + //TODO stocker les données dans le localStorage } useEffect(() => { const handleNewFriendRequest = (request: any) => { - console.log("request", request); saveReceivedRequest(request); } diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index fbc8964..d3d51ca 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -38,7 +38,6 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-received', (event) => { const data = JSON.parse(event.data); - console.log(data); onRequestReceived(data); }) eventSource.onerror = (error) => { From 4901a5754d91b72c26443446078df4db56376dc7 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 21:54:51 +0100 Subject: [PATCH 081/118] :sparkles: request accepted by the other one is now good to be saved --- src/App.tsx | 33 +++++++++++++++++++++++-- src/components/add-friend.component.tsx | 1 - src/services/friend-request.service.ts | 11 ++++----- 3 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 69d2acd..d992f5a 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,7 @@ import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; import { useEffect } from "react"; import Notification from "./types/notification"; -import { eventFetchFriendRequests } from "./services/friend-request.service"; +import { eventAcceptedFriendRequest, eventFetchFriendRequests } from "./services/friend-request.service"; export default function App() { @@ -20,9 +20,23 @@ export default function App() { //TODO stocker les données dans le localStorage } + function saveAcceptedRequest(request: string) { + const notification: Notification = { + id: crypto.randomUUID(), + type: "friend-request-accepted", + emitterId: request, + content: request + ' accepted to be your friend', + receivedAt: new Date().toISOString(), + } + + console.log(notification); + + //TODO stocker les données dans le localStorage + } + useEffect(() => { const handleNewFriendRequest = (request: any) => { - saveReceivedRequest(request); + saveAcceptedRequest(request); } const eventSource = eventFetchFriendRequests(handleNewFriendRequest); @@ -32,6 +46,21 @@ export default function App() { }; }, []) + useEffect(() => { + console.log("notifications"); + + const handleAcceptedFriendRequest = (request: any) => { + saveReceivedRequest(request); + console.log("success"); + } + + const eventSource = eventAcceptedFriendRequest(handleAcceptedFriendRequest); + + return () => { + eventSource.close(); + }; + }, []) + return (
diff --git a/src/components/add-friend.component.tsx b/src/components/add-friend.component.tsx index ebcffa1..8a9e4b3 100644 --- a/src/components/add-friend.component.tsx +++ b/src/components/add-friend.component.tsx @@ -1,4 +1,3 @@ -import { useEffect } from "react"; import { useUserStore } from "../stores/user.store"; import { SubmitHandler, useForm } from "react-hook-form"; import { sendFriendRequest } from "../services/friend-request.service"; diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index d3d51ca..1a5aaa1 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -1,4 +1,3 @@ -import { FriendRequestAcceptedDTO } from "../dtos/friend-request-accepted.dto"; import { FriendRequest } from "../types/friend-request"; export async function fetchFriendRequests() { @@ -14,7 +13,7 @@ export async function fetchFriendRequests() { export const sendFriendRequest = async (receiverId: string) => { const randomuuid = crypto.randomUUID(); - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { + await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${randomuuid}`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -25,7 +24,7 @@ export const sendFriendRequest = async (receiverId: string) => { } export const acceptRequest = async (requestId: string) => { - const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${requestId}/accept`, { + await fetch(`${process.env.REACT_APP_API_BASE_URL}/social/friend-request/${requestId}/accept`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -34,7 +33,7 @@ export const acceptRequest = async (requestId: string) => { }) } -export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest) => void) => { +export const eventFetchFriendRequests = (onRequestReceived: (data: any) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-received', (event) => { const data = JSON.parse(event.data); @@ -46,13 +45,13 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: FriendRequest return eventSource; } -export const eventAcceptedFriendRequest = (onRequestAccepted: (data: FriendRequestAcceptedDTO) => void) => { +export const eventAcceptedFriendRequest = (onRequestAccepted: (data: {userId: string}) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-accepted', (event) => { const data = JSON.parse(event.data); + console.log(data); onRequestAccepted(data); }) - eventSource.onerror = (error) => { eventSource.close(); }; From 32f840d24e872324064c3e892138822d9b90f189 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Wed, 6 Nov 2024 23:53:28 +0100 Subject: [PATCH 082/118] :sparkles: notifications stored in the localStorage --- src/App.tsx | 32 ++++++++----------- src/components/add-friend.component.tsx | 2 +- .../friend-request-card.component.tsx | 20 ++++++++++++ src/dtos/friend-request-accepted.dto.ts | 5 --- src/dtos/friend-request-received.dto.ts | 3 -- src/pages/notifications.page.tsx | 3 +- src/services/friend-request.service.ts | 3 +- src/types/notification.ts | 1 + 8 files changed, 39 insertions(+), 30 deletions(-) delete mode 100644 src/dtos/friend-request-accepted.dto.ts delete mode 100644 src/dtos/friend-request-received.dto.ts diff --git a/src/App.tsx b/src/App.tsx index d992f5a..567a38f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,30 +13,29 @@ export default function App() { emitterId: request.senderId, content: request.senderId + ' asked you in friend', receivedAt: request.requestedAt, + didIAccept: false } - - console.log(notification); - - //TODO stocker les données dans le localStorage + const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + const updatedNotifications = [notification, ...existingNotifications]; + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); } - - function saveAcceptedRequest(request: string) { + + function saveAcceptedRequest(request: any) { const notification: Notification = { id: crypto.randomUUID(), type: "friend-request-accepted", - emitterId: request, - content: request + ' accepted to be your friend', + emitterId: request.userId, + content: request.userId + ' accepted to be your friend', receivedAt: new Date().toISOString(), } - - console.log(notification); - - //TODO stocker les données dans le localStorage - } + const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + const updatedNotifications = [notification, ...existingNotifications]; + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + } useEffect(() => { const handleNewFriendRequest = (request: any) => { - saveAcceptedRequest(request); + saveReceivedRequest(request); } const eventSource = eventFetchFriendRequests(handleNewFriendRequest); @@ -47,11 +46,8 @@ export default function App() { }, []) useEffect(() => { - console.log("notifications"); - const handleAcceptedFriendRequest = (request: any) => { - saveReceivedRequest(request); - console.log("success"); + saveAcceptedRequest(request); } const eventSource = eventAcceptedFriendRequest(handleAcceptedFriendRequest); diff --git a/src/components/add-friend.component.tsx b/src/components/add-friend.component.tsx index 8a9e4b3..2308e37 100644 --- a/src/components/add-friend.component.tsx +++ b/src/components/add-friend.component.tsx @@ -10,7 +10,7 @@ export default function AddFriend() { const user = useUserStore(); - const { register, handleSubmit, reset, formState: { errors } } = useForm({ + const { register, handleSubmit, reset } = useForm({ defaultValues: { content: "" } diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index e949ac7..500e2e4 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -7,6 +7,26 @@ export default function FriendRequestCard(request: FriendRequest) { async function acceptFriendRequest() { await acceptRequest(request.id.toString()); + const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + console.log(notifications); + + console.log(request.id); + + + // changer la valeur de didIAccept a true + const updatedNotifications = notifications.map((notification: any) => { + console.log(updatedNotifications); + if (notification.id === request.id) { + return { + ...notification, + didIAccept: true + }; + } + return notification; + }); + + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + window.location.reload(); } diff --git a/src/dtos/friend-request-accepted.dto.ts b/src/dtos/friend-request-accepted.dto.ts deleted file mode 100644 index 012dbf7..0000000 --- a/src/dtos/friend-request-accepted.dto.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface FriendRequestAcceptedDTO { - id: string, - senderId: string, - requestedAt: string -} diff --git a/src/dtos/friend-request-received.dto.ts b/src/dtos/friend-request-received.dto.ts deleted file mode 100644 index de5f94a..0000000 --- a/src/dtos/friend-request-received.dto.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface FriendRequestReceivedDTO { - userId: string -} diff --git a/src/pages/notifications.page.tsx b/src/pages/notifications.page.tsx index a0a4792..1ad11fc 100644 --- a/src/pages/notifications.page.tsx +++ b/src/pages/notifications.page.tsx @@ -1,9 +1,10 @@ import { useEffect } from "react"; export default function NotificationListPage() { + const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); useEffect(() => { - console.log("notifications"); + console.log(notifications); }, []); return ( diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index 1a5aaa1..f3d52e4 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -45,11 +45,10 @@ export const eventFetchFriendRequests = (onRequestReceived: (data: any) => void) return eventSource; } -export const eventAcceptedFriendRequest = (onRequestAccepted: (data: {userId: string}) => void) => { +export const eventAcceptedFriendRequest = (onRequestAccepted: (data: any) => void) => { const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) eventSource.addEventListener('friend-request-accepted', (event) => { const data = JSON.parse(event.data); - console.log(data); onRequestAccepted(data); }) eventSource.onerror = (error) => { diff --git a/src/types/notification.ts b/src/types/notification.ts index f8f9626..053eeb9 100644 --- a/src/types/notification.ts +++ b/src/types/notification.ts @@ -4,4 +4,5 @@ export default interface Notification { emitterId?: string; content?: string; receivedAt: string; + didIAccept?: boolean; } \ No newline at end of file From 42c00f2b65cfe2c155ec58757dc112152d03fada Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Thu, 7 Nov 2024 00:54:56 +0100 Subject: [PATCH 083/118] :sparkles: notification page is great --- src/App.tsx | 6 +- .../friend-request-card.component.tsx | 12 ++-- src/pages/notifications.page.tsx | 72 +++++++++++++++++-- src/types/notification.ts | 3 +- 4 files changed, 77 insertions(+), 16 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 567a38f..94375fd 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -11,9 +11,9 @@ export default function App() { id: request.id, type: "friend-request-received", emitterId: request.senderId, - content: request.senderId + ' asked you in friend', receivedAt: request.requestedAt, - didIAccept: false + didIAccept: false, + status: "pending-request", } const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const updatedNotifications = [notification, ...existingNotifications]; @@ -25,8 +25,8 @@ export default function App() { id: crypto.randomUUID(), type: "friend-request-accepted", emitterId: request.userId, - content: request.userId + ' accepted to be your friend', receivedAt: new Date().toISOString(), + status: "my-friend-request-accepted", } const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const updatedNotifications = [notification, ...existingNotifications]; diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index 500e2e4..32c105b 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -8,14 +8,8 @@ export default function FriendRequestCard(request: FriendRequest) { await acceptRequest(request.id.toString()); const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); - console.log(notifications); - - console.log(request.id); - - // changer la valeur de didIAccept a true const updatedNotifications = notifications.map((notification: any) => { - console.log(updatedNotifications); if (notification.id === request.id) { return { ...notification, @@ -26,7 +20,7 @@ export default function FriendRequestCard(request: FriendRequest) { }); localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - + window.location.reload(); } @@ -41,7 +35,9 @@ export default function FriendRequestCard(request: FriendRequest) { + onClick={acceptFriendRequest}> + Accept +
diff --git a/src/pages/notifications.page.tsx b/src/pages/notifications.page.tsx index 1ad11fc..14a222f 100644 --- a/src/pages/notifications.page.tsx +++ b/src/pages/notifications.page.tsx @@ -1,13 +1,77 @@ -import { useEffect } from "react"; +import { useEffect, useState } from "react"; +import { Friend } from "../types/friend"; +import { getUserFriends } from "../services/friend.service"; +import Notification from "../types/notification"; export default function NotificationListPage() { - const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + const [friends, setFriends] = useState([]); + const [notifications, setNotifications] = useState([]); useEffect(() => { - console.log(notifications); + const storedNotifications = localStorage.getItem('notifications'); + const parsedNotifications = storedNotifications ? JSON.parse(storedNotifications) : []; + setNotifications(parsedNotifications); + + getUserFriends().then(fetchedFriends => { + setFriends(fetchedFriends); + + const updatedNotifications = parsedNotifications.map((notification: Notification) => { + if (notification.didIAccept) { + const selectedFriend = fetchedFriends.find( + (friend: Friend) => friend.userId === notification.emitterId + ); + + if (selectedFriend) { + return { + ...notification, + status: "friend-accepted", + emitterUsername: selectedFriend.username + }; + } + } + return notification; + }); + + setNotifications(updatedNotifications); + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + }); }, []); + if (!notifications || notifications.length === 0) { + return ( +
+ Notifications +

No notifications to display

+
+ ); + } + return ( -
NotificationListPage
+
+ Notifications +
+ {notifications.map((notification: Notification) => ( +
+
+ {notification.status === 'friend-accepted' && ( + {notification.emitterUsername} accepted to be your friend + )} + {notification.status === 'my-friend-request-accepted' && ( + {notification.emitterId} accepted your friend request + )} + {notification.status === 'pending-request' && ( + {notification.emitterId} sent you a friend request + )} +
+
+ ))} +
+
); } \ No newline at end of file diff --git a/src/types/notification.ts b/src/types/notification.ts index 053eeb9..b5a7123 100644 --- a/src/types/notification.ts +++ b/src/types/notification.ts @@ -2,7 +2,8 @@ export default interface Notification { id: string; type: string; emitterId?: string; - content?: string; + emitterUsername?: string; receivedAt: string; didIAccept?: boolean; + status?: string; } \ No newline at end of file From 332c44b0dfd47148eca410bf4c267291cd5197b4 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Thu, 7 Nov 2024 00:58:03 +0100 Subject: [PATCH 084/118] :sparkles: for invitations send it's good too --- src/pages/notifications.page.tsx | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/pages/notifications.page.tsx b/src/pages/notifications.page.tsx index 14a222f..78b26f6 100644 --- a/src/pages/notifications.page.tsx +++ b/src/pages/notifications.page.tsx @@ -16,6 +16,19 @@ export default function NotificationListPage() { setFriends(fetchedFriends); const updatedNotifications = parsedNotifications.map((notification: Notification) => { + if (notification.status === "my-friend-request-accepted") { + const selectedFriend = fetchedFriends.find( + (friend: Friend) => friend.userId === notification.emitterId + ); + + if (selectedFriend) { + return { + ...notification, + status: "friend-accepted", + emitterUsername: selectedFriend.username + }; + } + } if (notification.didIAccept) { const selectedFriend = fetchedFriends.find( (friend: Friend) => friend.userId === notification.emitterId From 48e4c72956e6c0249f6fc9ae31b19a96721186d3 Mon Sep 17 00:00:00 2001 From: Smachy24 Date: Thu, 7 Nov 2024 09:55:03 +0100 Subject: [PATCH 085/118] feat: retry message button --- src/pages/chat.page.tsx | 11 ++++++----- src/services/message.service.ts | 31 +++++++++++++++++++++++++++++-- src/stores/message.store.ts | 6 +++--- 3 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index a8dc344..c445cae 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -10,8 +10,8 @@ import { MessageAdapter } from "../adapters/message.adapter"; import { BadRequestError } from "../errors/bad-request.error"; import { useUserStore } from "../stores/user.store"; -// const messageService : MessageAdapter = new FalseMessageService(); -const messageService: MessageAdapter = new MessageService(); +const messageService : MessageAdapter = new FalseMessageService(); +// const messageService: MessageAdapter = new MessageService(); export default function ChatPage() { @@ -25,7 +25,7 @@ export default function ChatPage() { const { receiverId } = useParams(); - const { messages, setMessages, addMessage, setErrorLastMessage } = useMessageStore(); + const { messages, setMessages, addMessage, updateErrorLastMessage } = useMessageStore(); const { id } = useUserStore(); const { register, handleSubmit, reset, formState: { errors } } = useForm({ @@ -42,7 +42,7 @@ export default function ChatPage() { navigate('/'); } else { - setErrorLastMessage(); + updateErrorLastMessage(true); } } } @@ -63,7 +63,8 @@ export default function ChatPage() { } const retryMessage = async (message: Message) => { - console.log(message); + updateErrorLastMessage(false); + await sendMessage(message); } useEffect(() => { diff --git a/src/services/message.service.ts b/src/services/message.service.ts index ddf2052..e961195 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -56,8 +56,35 @@ export class MessageService implements MessageAdapter { export class FalseMessageService implements MessageAdapter { - sendMessage(message: Message): Promise { - throw new Error("Method not implemented."); + async sendMessage(message: Message): Promise { + + const random = Math.random() * 10; + + if(random < 5) { + const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + credentials: 'include', + body: JSON.stringify({ + receiverId: message.receiverId, + content: message.content, + }), + }) + //TODO: Gérer plusieurs erreurs dont erreur connexion + if (response.status === 400) { + throw new BadRequestError("You are not friend with this user"); + } + else { + if (!response.ok) { + throw new Error(`Error ${response.status}: ${response.statusText}`); + } + } + } + else { + throw new Error("Method not implemented."); + } } fetchMessages = async (receiverId: string): Promise => { diff --git a/src/stores/message.store.ts b/src/stores/message.store.ts index 8328523..cf87dce 100644 --- a/src/stores/message.store.ts +++ b/src/stores/message.store.ts @@ -6,17 +6,17 @@ type MessageStore = { messages: Message[]; setMessages: (messages: Message[]) => void; addMessage: (message: Message) => void; - setErrorLastMessage: () => void; + updateErrorLastMessage: (error: boolean) => void; } export const useMessageStore = create((set) => ({ messages: [], setMessages: (messages: Message[]) => set({ messages }), addMessage: (message: Message) => set((state) => ({ messages: [...state.messages, message] })), - setErrorLastMessage: () => + updateErrorLastMessage: (error: boolean) => set((state) => { const messages = [...state.messages]; - messages[messages.length - 1] = {...messages[messages.length - 1], error: true}; + messages[messages.length - 1] = {...messages[messages.length - 1], error}; return { messages }; }), })) \ No newline at end of file From 8fb419a30093a1efffe3bf3a781207b1428bd6ed Mon Sep 17 00:00:00 2001 From: Smachy24 Date: Thu, 7 Nov 2024 10:44:44 +0100 Subject: [PATCH 086/118] feat: add link to messages --- src/pages/chat.page.tsx | 55 +++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 21 deletions(-) diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index c445cae..7a3fe91 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -10,8 +10,8 @@ import { MessageAdapter } from "../adapters/message.adapter"; import { BadRequestError } from "../errors/bad-request.error"; import { useUserStore } from "../stores/user.store"; -const messageService : MessageAdapter = new FalseMessageService(); -// const messageService: MessageAdapter = new MessageService(); +// const messageService : MessageAdapter = new FalseMessageService(); +const messageService: MessageAdapter = new MessageService(); export default function ChatPage() { @@ -82,26 +82,39 @@ export default function ChatPage() { }, [receiverId]); return ( +
-
- {messages.map((message, index) => ( - message.error ? ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} - -
- ) : - message.receiverId === receiverId ? ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) : ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) - ))} -
+ {messages.map((message, index) => { + const splitMessage = message.content.split(" "); + + const htmlMessage = splitMessage.map((word, i) => { + if (word.startsWith("http://") || word.startsWith("https://")) { + return ( + + {word} + + ); + } else { + return {word} ; + } + }); + + return message.error ? ( +
+ {htmlMessage} : {dateFormater(message.sendAt)} + +
+ ) : message.receiverId === receiverId ? ( +
+ {htmlMessage} : {dateFormater(message.sendAt)} +
+ ) : ( +
+ {htmlMessage} : {dateFormater(message.sendAt)} +
+ ); + })} +
From ce41773f5c6c09d2ea449f490e93d3590856c186 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Thu, 7 Nov 2024 11:53:08 +0100 Subject: [PATCH 087/118] :construction: notification unseen system is working but notification badge bug --- src/App.tsx | 11 +++++++++++ src/components/navigation.tsx | 15 ++++++++++++++- src/pages/notifications.page.tsx | 11 ++++++++++- src/stores/friend.store.ts | 4 +++- src/types/notification.ts | 1 + src/utils/count-unseen-notifications.tsx | 19 +++++++++++++++++++ 6 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 src/utils/count-unseen-notifications.tsx diff --git a/src/App.tsx b/src/App.tsx index 94375fd..75b073e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,6 +3,7 @@ import Navigation from "./components/navigation"; import { useEffect } from "react"; import Notification from "./types/notification"; import { eventAcceptedFriendRequest, eventFetchFriendRequests } from "./services/friend-request.service"; +import { countUnseenNotifications } from "./utils/count-unseen-notifications"; export default function App() { @@ -14,10 +15,16 @@ export default function App() { receivedAt: request.requestedAt, didIAccept: false, status: "pending-request", + isSeen: false, } const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const updatedNotifications = [notification, ...existingNotifications]; localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + + console.log("on passe ici ?"); + + + countUnseenNotifications(); } function saveAcceptedRequest(request: any) { @@ -27,10 +34,14 @@ export default function App() { emitterId: request.userId, receivedAt: new Date().toISOString(), status: "my-friend-request-accepted", + isSeen: false, } const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); const updatedNotifications = [notification, ...existingNotifications]; localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + + console.log("on passe ici ?"); + countUnseenNotifications(); } useEffect(() => { diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 8abbfef..47713f4 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -3,6 +3,8 @@ import { Link } from "react-router-dom"; import { logoutUser } from "../services/auth.service"; import { useFriendStore } from "../stores/friend.store"; import { useUserStore } from "../stores/user.store"; +import { countUnseenNotifications } from "../utils/count-unseen-notifications"; +import { useEffect } from "react"; export default function Navigation() { const { clearUser } = useUserStore(); @@ -12,11 +14,22 @@ export default function Navigation() { clearFriends(); logoutUser(); }; + + let test = localStorage.getItem("unseenNotif"); + + useEffect(() => { + countUnseenNotifications(); + }, [test]); + return (
Chats Friends - Notifications +
+ Notifications + + {countUnseenNotifications()} +
Logout diff --git a/src/pages/notifications.page.tsx b/src/pages/notifications.page.tsx index 78b26f6..934bd88 100644 --- a/src/pages/notifications.page.tsx +++ b/src/pages/notifications.page.tsx @@ -2,6 +2,8 @@ import { useEffect, useState } from "react"; import { Friend } from "../types/friend"; import { getUserFriends } from "../services/friend.service"; import Notification from "../types/notification"; +import { countUnseenNotifications } from "../utils/count-unseen-notifications"; +import { useFriendStore } from "../stores/friend.store"; export default function NotificationListPage() { const [friends, setFriends] = useState([]); @@ -16,6 +18,12 @@ export default function NotificationListPage() { setFriends(fetchedFriends); const updatedNotifications = parsedNotifications.map((notification: Notification) => { + if (notification.isSeen == false) { + return { + ...notification, + isSeen: "true" + }; + } if (notification.status === "my-friend-request-accepted") { const selectedFriend = fetchedFriends.find( (friend: Friend) => friend.userId === notification.emitterId @@ -47,6 +55,7 @@ export default function NotificationListPage() { setNotifications(updatedNotifications); localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + countUnseenNotifications(); }); }, []); @@ -73,7 +82,7 @@ export default function NotificationListPage() { } > {notification.status === 'friend-accepted' && ( - {notification.emitterUsername} accepted to be your friend + {notification.emitterUsername}'s request approved by you )} {notification.status === 'my-friend-request-accepted' && ( {notification.emitterId} accepted your friend request diff --git a/src/stores/friend.store.ts b/src/stores/friend.store.ts index f118f32..5236a99 100644 --- a/src/stores/friend.store.ts +++ b/src/stores/friend.store.ts @@ -9,12 +9,14 @@ type Actions = { setFriends: (friends: Friend[]) => void; addFriend: (friend: Friend) => void; clearFriends: () => void; + // fetchFriends: () => Friend[]; }; -export const useFriendStore = create((set) => ({ +export const useFriendStore = create((set, get) => ({ friends: [], setFriends: (friends: Friend[]) => set({ friends }), addFriend: (friend: Friend) => set((state) => ({ friends: [...state.friends, friend] })), clearFriends: () => set({ friends: [] }), + // fetchFriends: () => get().friends, })); diff --git a/src/types/notification.ts b/src/types/notification.ts index b5a7123..8e95722 100644 --- a/src/types/notification.ts +++ b/src/types/notification.ts @@ -6,4 +6,5 @@ export default interface Notification { receivedAt: string; didIAccept?: boolean; status?: string; + isSeen: false; } \ No newline at end of file diff --git a/src/utils/count-unseen-notifications.tsx b/src/utils/count-unseen-notifications.tsx new file mode 100644 index 0000000..526af2f --- /dev/null +++ b/src/utils/count-unseen-notifications.tsx @@ -0,0 +1,19 @@ +import Notification from "../types/notification"; + +export function countUnseenNotifications() { + console.log("count"); + + const storedNotifications = localStorage.getItem('notifications'); + const parsedNotifications = storedNotifications ? JSON.parse(storedNotifications) : []; + + let count = 0; + parsedNotifications.map((notification: Notification) => { + if (notification.isSeen == false || notification.didIAccept == false) { + count = count + 1; + } + }); + console.log(count); + + localStorage.setItem('unseenNotif', JSON.stringify(count)); + return count.toString(); +} \ No newline at end of file From c5043e32bacee42b2dd6c399891102bd27a38a88 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 12:08:33 +0100 Subject: [PATCH 088/118] feat : button component and input styles --- src/components/buttons/button.tsx | 26 ++++++++++ src/index.css | 80 ++++++++++++++++++++++++++++++- tailwind.config.js | 8 ++-- 3 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/components/buttons/button.tsx diff --git a/src/components/buttons/button.tsx b/src/components/buttons/button.tsx new file mode 100644 index 0000000..e6a5d85 --- /dev/null +++ b/src/components/buttons/button.tsx @@ -0,0 +1,26 @@ +interface ButtonProps { + label: string; + onClick?: (params?: any) => any; + type?: "submit"; + disabled?: boolean; + variant?: "primary" | "secondary" | "tertiary"; +} + +export default function Button({ + label, + onClick, + type, + disabled, + variant = "primary", +}: ButtonProps) { + return ( + + ); +} diff --git a/src/index.css b/src/index.css index bd6213e..2a23e8b 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,81 @@ @tailwind base; @tailwind components; -@tailwind utilities; \ No newline at end of file +@tailwind utilities; + +@layer components { + .btn { + @apply relative px-8 py-1 rounded-full overflow-hidden transition-all duration-300 outline-none font-semibold; + } + + .btn:hover, + .btn:focus { + @apply scale-105; + } + + .btn:active { + @apply scale-95; + } + .primary-btn { + @apply btn border-2 border-cyan-400; + } + + .primary-btn::before { + @apply absolute content-[""] w-full h-full top-0 left-0 bg-slate-200 rounded-full z-[-2] shadow-[inset_0px_-4px_8px_4px_rgba(0,0,0,0.1)]; + } + + .primary-btn::after { + @apply absolute content-[""] top-[-4px] w-[90%] h-[60%] bg-white left-1/2 transform -translate-x-1/2 rounded-full z-[-1]; + } + + .primary-btn:hover, + .primary-btn:focus { + @apply shadow-lg shadow-cyan-100; + } + + .primary-btn:active { + @apply shadow-none; + } + + .primary-btn:disabled { + @apply cursor-not-allowed opacity-50 border-slate-500 pointer-events-none; + } + + .secondary-btn { + @apply btn bg-white rounded-full font-semibold shadow-[inset_0_-5px_8px_4px_rgba(0,0,0,0.1),0_0px_4px_1px_rgba(0,0,0,0.2),inset_0px_-3px_0px_rgba(255,255,255,1)] transition-all duration-300; + } + + .tertiary-btn { + @apply btn; + } + + .input-group { + @apply flex flex-col gap-1; + } + + .input-group { + @apply font-semibold text-slate-500; + } + .input-group input { + @apply px-4 py-1 rounded-lg border-2 shadow-[inset_0px_-8px_8px_0px_rgba(0,0,0,0.2)]; + } + + .input-group input:focus { + @apply outline-2 outline-cyan-400 ring-[3px] ring-cyan-500; + } +} +@layer { + body { + /* background-image: linear-gradient( + 0deg, + #ededed 41.67%, + #ffffff 41.67%, + #ffffff 50%, + #ededed 50%, + #ededed 91.67%, + #ffffff 91.67%, + #ffffff 100% + ); + background-size: 24px 24px; */ + @apply bg-slate-100 text-slate-600; + } +} diff --git a/tailwind.config.js b/tailwind.config.js index c0958ec..9e12a2f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,11 +1,9 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: [ - "./src/**/*.{js,jsx,ts,tsx}", - ], + content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { extend: {}, }, + safelist: ["btn", "primary-btn", "secondary-btn", "tertiary-btn"], plugins: [], -} - +}; From b1d6e04ac122887b23f8c96608fe6d13795eeef0 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Thu, 7 Nov 2024 12:10:29 +0100 Subject: [PATCH 089/118] :art: move event in app into component notifications Co-authored-by: Diego --- src/App.tsx | 67 +------------------ .../friend-request-accepted.component.tsx | 41 ++++++++++++ .../friend-request-received.component.tsx | 38 +++++++++++ .../notifications/notifications.component.tsx | 11 +++ 4 files changed, 92 insertions(+), 65 deletions(-) create mode 100644 src/components/notifications/friend-request-accepted.component.tsx create mode 100644 src/components/notifications/friend-request-received.component.tsx create mode 100644 src/components/notifications/notifications.component.tsx diff --git a/src/App.tsx b/src/App.tsx index 75b073e..3d79481 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,75 +1,12 @@ import { Outlet } from "react-router-dom"; import Navigation from "./components/navigation"; -import { useEffect } from "react"; -import Notification from "./types/notification"; -import { eventAcceptedFriendRequest, eventFetchFriendRequests } from "./services/friend-request.service"; -import { countUnseenNotifications } from "./utils/count-unseen-notifications"; +import Notifications from "./components/notifications/notifications.component"; export default function App() { - - function saveReceivedRequest(request: any) { - const notification: Notification = { - id: request.id, - type: "friend-request-received", - emitterId: request.senderId, - receivedAt: request.requestedAt, - didIAccept: false, - status: "pending-request", - isSeen: false, - } - const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); - const updatedNotifications = [notification, ...existingNotifications]; - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - - console.log("on passe ici ?"); - - - countUnseenNotifications(); - } - - function saveAcceptedRequest(request: any) { - const notification: Notification = { - id: crypto.randomUUID(), - type: "friend-request-accepted", - emitterId: request.userId, - receivedAt: new Date().toISOString(), - status: "my-friend-request-accepted", - isSeen: false, - } - const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); - const updatedNotifications = [notification, ...existingNotifications]; - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - - console.log("on passe ici ?"); - countUnseenNotifications(); - } - - useEffect(() => { - const handleNewFriendRequest = (request: any) => { - saveReceivedRequest(request); - } - - const eventSource = eventFetchFriendRequests(handleNewFriendRequest); - - return () => { - eventSource.close(); - }; - }, []) - - useEffect(() => { - const handleAcceptedFriendRequest = (request: any) => { - saveAcceptedRequest(request); - } - - const eventSource = eventAcceptedFriendRequest(handleAcceptedFriendRequest); - - return () => { - eventSource.close(); - }; - }, []) return (
+
diff --git a/src/components/notifications/friend-request-accepted.component.tsx b/src/components/notifications/friend-request-accepted.component.tsx new file mode 100644 index 0000000..633d422 --- /dev/null +++ b/src/components/notifications/friend-request-accepted.component.tsx @@ -0,0 +1,41 @@ +import { useEffect } from "react"; +import { eventFetchFriendRequests } from "../../services/friend-request.service"; +import { countUnseenNotifications } from "../../utils/count-unseen-notifications"; +import Notification from "../../types/notification"; + +export default function FriendRequestReceived() { + + function saveReceivedRequest(request: any) { + const notification: Notification = { + id: request.id, + type: "friend-request-received", + emitterId: request.senderId, + receivedAt: request.requestedAt, + didIAccept: false, + status: "pending-request", + isSeen: false, + } + const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + const updatedNotifications = [notification, ...existingNotifications]; + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + + console.log("on passe ici ?"); + + + countUnseenNotifications(); + } + + useEffect(() => { + const handleNewFriendRequest = (request: any) => { + saveReceivedRequest(request); + } + + const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + + return () => { + eventSource.close(); + }; + }, []) + + return null +} \ No newline at end of file diff --git a/src/components/notifications/friend-request-received.component.tsx b/src/components/notifications/friend-request-received.component.tsx new file mode 100644 index 0000000..c590263 --- /dev/null +++ b/src/components/notifications/friend-request-received.component.tsx @@ -0,0 +1,38 @@ +import { useEffect } from "react"; +import { eventAcceptedFriendRequest } from "../../services/friend-request.service"; +import { countUnseenNotifications } from "../../utils/count-unseen-notifications"; +import Notification from "../../types/notification"; + +export default function FriendRequestAccepted() { + + function saveAcceptedRequest(request: any) { + const notification: Notification = { + id: crypto.randomUUID(), + type: "friend-request-accepted", + emitterId: request.userId, + receivedAt: new Date().toISOString(), + status: "my-friend-request-accepted", + isSeen: false, + } + const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + const updatedNotifications = [notification, ...existingNotifications]; + localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); + countUnseenNotifications(); + } + + + + useEffect(() => { + const handleAcceptedFriendRequest = (request: any) => { + saveAcceptedRequest(request); + } + + const eventSource = eventAcceptedFriendRequest(handleAcceptedFriendRequest); + + return () => { + eventSource.close(); + }; + }, []) + + return null +} \ No newline at end of file diff --git a/src/components/notifications/notifications.component.tsx b/src/components/notifications/notifications.component.tsx new file mode 100644 index 0000000..94845aa --- /dev/null +++ b/src/components/notifications/notifications.component.tsx @@ -0,0 +1,11 @@ +import FriendRequestReceived from "./friend-request-accepted.component"; +import FriendRequestAccepted from "./friend-request-received.component"; + +export default function Notifications() { + return ( + <> + + + + ) +} \ No newline at end of file From 39b81ca46bf28214d05aefe37646773c3c99e128 Mon Sep 17 00:00:00 2001 From: Mathis Zerari Date: Thu, 7 Nov 2024 12:19:14 +0100 Subject: [PATCH 090/118] :construction: in progress --- src/components/friend-request-card.component.tsx | 3 ++- src/stores/notification.store.ts | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/stores/notification.store.ts diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index 32c105b..edc0a4e 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -1,5 +1,6 @@ import { acceptRequest } from "../services/friend-request.service"; import { FriendRequest } from "../types/friend-request"; +import Notification from "../types/notification"; import { dateFormater } from "../utils/dateFormater"; export default function FriendRequestCard(request: FriendRequest) { @@ -9,7 +10,7 @@ export default function FriendRequestCard(request: FriendRequest) { const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); - const updatedNotifications = notifications.map((notification: any) => { + const updatedNotifications = notifications.map((notification: Notification) => { if (notification.id === request.id) { return { ...notification, diff --git a/src/stores/notification.store.ts b/src/stores/notification.store.ts new file mode 100644 index 0000000..883f48a --- /dev/null +++ b/src/stores/notification.store.ts @@ -0,0 +1,12 @@ +import { create } from "zustand/react"; + +import Notification from "../types/notification"; + +type NotificationsStore = { + notifications: Notification[]; + addNotification: (notification: Notification) => void; +} +export const useNotificationStore = create((set) => ({ + notifications: [], + addNotification: (notification: Notification) => set((state) => ({ notifications: [notification, ...state.notifications] })), +})) \ No newline at end of file From 8e288976552585a7b7789ba935b397ee17ef28b6 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 15:06:58 +0100 Subject: [PATCH 091/118] feat : add login and register style --- src/components/buttons/button.tsx | 8 +++--- src/components/forms/auth.form.tsx | 40 ++++++++++++++++++++++-------- src/index.css | 36 ++++++++++++++++----------- src/pages/login.page.tsx | 21 ++++++++++------ src/pages/register.page.tsx | 19 ++++++++++---- 5 files changed, 84 insertions(+), 40 deletions(-) diff --git a/src/components/buttons/button.tsx b/src/components/buttons/button.tsx index e6a5d85..0d98284 100644 --- a/src/components/buttons/button.tsx +++ b/src/components/buttons/button.tsx @@ -1,21 +1,23 @@ interface ButtonProps { label: string; onClick?: (params?: any) => any; - type?: "submit"; + type?: "submit" | "button"; disabled?: boolean; variant?: "primary" | "secondary" | "tertiary"; + className?: string; } export default function Button({ label, onClick, - type, + type = "button", disabled, variant = "primary", + className, }: ButtonProps) { return ( + {children ?? children} + - +
+

+ Wii Login +

+ +
); } diff --git a/src/pages/register.page.tsx b/src/pages/register.page.tsx index ba58971..5158a04 100644 --- a/src/pages/register.page.tsx +++ b/src/pages/register.page.tsx @@ -2,6 +2,7 @@ import { useNavigate } from "react-router-dom"; import { registerUser } from "../services/auth.service"; import AuthForm from "../components/forms/auth.form"; import { UserDTO } from "../dtos/user.dto"; +import Button from "../components/buttons/button"; export default function RegisterPage() { const navigate = useNavigate(); @@ -11,10 +12,18 @@ export default function RegisterPage() { navigate("/"); }; return ( - <> -

Inscription

- - - +
+

+ Wii Register +

+ +
); } From f86b9e6d23c5e0543209e9a35ad79211721c30de Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 16:28:01 +0100 Subject: [PATCH 092/118] feat : add footer style --- package-lock.json | 9 +++++++ package.json | 1 + src/App.tsx | 4 +-- src/components/buttons/icon.button.tsx | 17 +++++++++++++ src/components/navigation.tsx | 35 ++++++++++++++++++++------ src/index.css | 12 +++++++++ 6 files changed, 68 insertions(+), 10 deletions(-) create mode 100644 src/components/buttons/icon.button.tsx diff --git a/package-lock.json b/package-lock.json index 12e01a8..8e312ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,7 @@ "@types/node": "^16.18.119", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "lucide-react": "^0.454.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", @@ -11259,6 +11260,14 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.454.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz", + "integrity": "sha512-hw7zMDwykCLnEzgncEEjHeA6+45aeEzRYuKHuyRSOPkhko+J3ySGjGIzu+mmMfDFG1vazHepMaYFYHbTFAZAAQ==", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", diff --git a/package.json b/package.json index e99c9ce..e849e26 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "@types/node": "^16.18.119", "@types/react": "^18.3.12", "@types/react-dom": "^18.3.1", + "lucide-react": "^0.454.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.53.1", diff --git a/src/App.tsx b/src/App.tsx index b4af07e..e2b8a23 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -3,9 +3,9 @@ import Navigation from "./components/navigation"; export default function App() { return ( -
- +
+
); } diff --git a/src/components/buttons/icon.button.tsx b/src/components/buttons/icon.button.tsx new file mode 100644 index 0000000..b83d2e4 --- /dev/null +++ b/src/components/buttons/icon.button.tsx @@ -0,0 +1,17 @@ +interface IconButtonProps { + children?: React.ReactNode; + onClick: (params?: any) => any; + className?: string; +} + +export default function IconButton({ + children, + onClick, + className, +}: IconButtonProps) { + return ( + + ); +} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index f9a3bef..df2fb43 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -1,8 +1,12 @@ -import * as React from "react"; -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import { logoutUser } from "../services/auth.service"; import { useFriendStore } from "../stores/friend.store"; import { useUserStore } from "../stores/user.store"; +import IconButton from "./buttons/icon.button"; + +import { DoorOpen } from "lucide-react"; +import { Users } from "lucide-react"; +import { Mail } from "lucide-react"; export default function Navigation() { const { clearUser } = useUserStore(); @@ -11,14 +15,29 @@ export default function Navigation() { clearUser(); clearFriends(); logoutUser(); + navigate("/login"); }; + const navigate = useNavigate(); + + const handleNotifications = () => {}; return ( -
- Chats - Friends - - Logout - +
+
+ navigate("/")}> +
Chats
+
+ navigate("/friends")}> + + +
+
+ handleNotifications()}> + + + handleLogout()}> + + +
); } diff --git a/src/index.css b/src/index.css index 2726131..a3f5f6e 100644 --- a/src/index.css +++ b/src/index.css @@ -44,6 +44,18 @@ @apply btn bg-white rounded-full font-semibold shadow-[inset_0_-5px_8px_4px_rgba(0,0,0,0.1),0_0px_4px_1px_rgba(0,0,0,0.2),inset_0px_-3px_0px_rgba(255,255,255,1)] transition-all duration-300; } + .icon-btn { + @apply btn w-fit aspect-square h-fit border-2 border-cyan-400 font-bold text-2xl text-slate-400 shadow-lg flex justify-center items-center; + } + + .icon-btn::before { + @apply absolute content-[""] w-full h-full top-0 left-0 bg-slate-200 rounded-full z-[-2] shadow-[inset_0px_-8px_8px_4px_rgba(0,0,0,0.2)]; + } + + .icon-btn::after { + @apply absolute content-[""] top-[-4px] w-[90%] h-[60%] bg-slate-100 left-1/2 transform -translate-x-1/2 rounded-full z-[-1]; + } + .tertiary-btn { @apply btn; } From e71ad726dd4c39a076743930545b8fefe2e27f61 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 18:17:30 +0100 Subject: [PATCH 093/118] feat : make the navigation bar sticky --- src/components/navigation.tsx | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index df2fb43..d1bfde8 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -21,22 +21,24 @@ export default function Navigation() { const handleNotifications = () => {}; return ( -
-
- navigate("/")}> -
Chats
-
- navigate("/friends")}> - - -
-
- handleNotifications()}> - - - handleLogout()}> - - +
+
+
+ navigate("/")}> +
Chats
+
+ navigate("/friends")}> + + +
+
+ handleNotifications()}> + + + handleLogout()}> + + +
); From 9c3cb1d95dd99f313530942097bf67ca8f9662cb Mon Sep 17 00:00:00 2001 From: Diego <108872676+Smachy24@users.noreply.github.com> Date: Thu, 7 Nov 2024 19:53:36 +0100 Subject: [PATCH 094/118] Feat/notify accepted friend request (#21) * :construction: notification system is on the way * fix: notifications on each pages * fix: remove force reload when accepting a friend request * nothing but i have to commit * fix: notification friend request * fix: clear localstorage on logout * fix: remove commment --------- Co-authored-by: Mathis Zerari <97899958+mathiszerari@users.noreply.github.com> Co-authored-by: Mathis Zerari --- src/App.tsx | 13 ++- src/adapters/message.adapter.ts | 1 - .../friend-request-card.component.tsx | 20 ++-- .../guards/procteded-route.guard.tsx | 38 +++++--- .../loaders/notifications.loader.tsx | 14 +++ .../{ => spinner}/loader.component.tsx | 0 .../loaders/{ => spinner}/loader.css | 0 src/components/navigation.tsx | 11 ++- .../friend-request-accepted.component.tsx | 38 ++++---- .../friend-request-received.component.tsx | 46 +++++---- .../notifications/notifications.component.tsx | 13 ++- src/index.tsx | 2 + src/pages/chat.page.tsx | 40 +++++--- src/pages/friend.page.tsx | 29 ++++-- src/pages/home.page.tsx | 28 +++--- src/pages/notifications.page.tsx | 96 ++++++++++--------- src/services/auth.service.ts | 2 + src/services/friend-request.service.ts | 24 ----- src/services/message.service.ts | 33 +------ src/services/notification.service.ts | 22 +++++ src/stores/notification.store.ts | 26 ++++- src/types/notification.ts | 2 +- src/utils/count-unseen-notifications.tsx | 12 +-- 23 files changed, 286 insertions(+), 224 deletions(-) create mode 100644 src/components/loaders/notifications.loader.tsx rename src/components/loaders/{ => spinner}/loader.component.tsx (100%) rename src/components/loaders/{ => spinner}/loader.css (100%) create mode 100644 src/services/notification.service.ts diff --git a/src/App.tsx b/src/App.tsx index 3d79481..6533ad6 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,14 +1,19 @@ -import { Outlet } from "react-router-dom"; +import { Outlet, useLoaderData } from "react-router-dom"; +import { useState } from "react"; + +import Notification from "./types/notification"; + import Navigation from "./components/navigation"; import Notifications from "./components/notifications/notifications.component"; +import { useNotificationStore } from "./stores/notification.store"; export default function App() { - + return (
- +
); -} \ No newline at end of file +} diff --git a/src/adapters/message.adapter.ts b/src/adapters/message.adapter.ts index bd66f6f..6799064 100644 --- a/src/adapters/message.adapter.ts +++ b/src/adapters/message.adapter.ts @@ -3,5 +3,4 @@ import Message from "../types/message"; export interface MessageAdapter { sendMessage: (message: Message) => Promise; fetchMessages: (receiverId: string) => Promise; - eventFetchMessages: (onMessageReceived: (data: Message) => void) => EventSource; } \ No newline at end of file diff --git a/src/components/friend-request-card.component.tsx b/src/components/friend-request-card.component.tsx index edc0a4e..0e2a2fb 100644 --- a/src/components/friend-request-card.component.tsx +++ b/src/components/friend-request-card.component.tsx @@ -1,28 +1,30 @@ import { acceptRequest } from "../services/friend-request.service"; +import { useNotificationStore } from "../stores/notification.store"; import { FriendRequest } from "../types/friend-request"; import Notification from "../types/notification"; import { dateFormater } from "../utils/dateFormater"; -export default function FriendRequestCard(request: FriendRequest) { +export default function FriendRequestCard({request, removeFriendRequest} : {request: FriendRequest, removeFriendRequest : (friendRequest: FriendRequest) => void}) { + const { notifications, setNotifications } = useNotificationStore(); - async function acceptFriendRequest() { - await acceptRequest(request.id.toString()); - const notifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + async function updateAcceptedRequestsNotifications() { + await acceptRequest(request.id.toString()); const updatedNotifications = notifications.map((notification: Notification) => { if (notification.id === request.id) { return { ...notification, - didIAccept: true + didIAccept: true, + isSeen: true, }; } return notification; }); - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - - window.location.reload(); + setNotifications(updatedNotifications) + removeFriendRequest(request) + } return ( @@ -36,7 +38,7 @@ export default function FriendRequestCard(request: FriendRequest) { diff --git a/src/components/guards/procteded-route.guard.tsx b/src/components/guards/procteded-route.guard.tsx index 339d81c..ee41ebf 100644 --- a/src/components/guards/procteded-route.guard.tsx +++ b/src/components/guards/procteded-route.guard.tsx @@ -1,27 +1,37 @@ -import { Outlet, useNavigate } from "react-router-dom"; +import { Outlet, useLoaderData, useNavigate } from "react-router-dom"; +import { useEffect } from "react"; import { checkUserAuth, getCurrentUser } from "../../services/auth.service"; import { useUserStore } from "../../stores/user.store"; import { useFriendStore } from "../../stores/friend.store"; +import { useNotificationStore } from "../../stores/notification.store"; +import Notification from "../../types/notification"; export default function ProtectedRoute() { const navigate = useNavigate(); const { id, updateUser, clearUser } = useUserStore(); const { clearFriends } = useFriendStore(); - const checkAuth = async () => { - const isAuth = await checkUserAuth(); - if (!isAuth) { - clearUser(); - clearFriends(); - navigate("/login"); - } + const { setNotifications } = useNotificationStore(); - if (!id && isAuth) { - const currentUser = await getCurrentUser(); - updateUser(currentUser); - } - }; + useEffect(() => { + const checkAuth = async () => { + const isAuth = await checkUserAuth(); + if (!isAuth) { + clearUser(); + clearFriends(); + navigate("/login"); + } else if (!id && isAuth) { + const currentUser = await getCurrentUser(); + updateUser(currentUser); + } + }; - checkAuth(); + checkAuth(); + }, [id, navigate, clearUser, clearFriends, updateUser]); + + const initialNotifications = useLoaderData() as Notification[]; + useEffect(() => { + setNotifications(initialNotifications); + }, [initialNotifications, setNotifications]); return ; } diff --git a/src/components/loaders/notifications.loader.tsx b/src/components/loaders/notifications.loader.tsx new file mode 100644 index 0000000..4ac0fc2 --- /dev/null +++ b/src/components/loaders/notifications.loader.tsx @@ -0,0 +1,14 @@ +import Notification from "../../types/notification"; + +export const NotificationsLoader = ({ + params, +}: { + params: Record; +}) => { + const storedNotifications: string | null = + localStorage.getItem("notifications"); + const parsedNotifications: Notification[] = storedNotifications + ? JSON.parse(storedNotifications) + : []; + return parsedNotifications; +}; diff --git a/src/components/loaders/loader.component.tsx b/src/components/loaders/spinner/loader.component.tsx similarity index 100% rename from src/components/loaders/loader.component.tsx rename to src/components/loaders/spinner/loader.component.tsx diff --git a/src/components/loaders/loader.css b/src/components/loaders/spinner/loader.css similarity index 100% rename from src/components/loaders/loader.css rename to src/components/loaders/spinner/loader.css diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 6275b79..dccc81e 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -5,21 +5,22 @@ import { useFriendStore } from "../stores/friend.store"; import { useUserStore } from "../stores/user.store"; import { countUnseenNotifications } from "../utils/count-unseen-notifications"; import { useEffect } from "react"; +import { useNotificationStore } from "../stores/notification.store"; export default function Navigation() { const { clearUser } = useUserStore(); const { clearFriends } = useFriendStore(); + const { notifications } = useNotificationStore(); + const handleLogout = () => { clearUser(); clearFriends(); logoutUser(); }; - let test = localStorage.getItem("unseenNotif"); - useEffect(() => { - countUnseenNotifications(); - }, [test]); + countUnseenNotifications(notifications); + }, [notifications]); return (
@@ -28,7 +29,7 @@ export default function Navigation() {
Notifications - {countUnseenNotifications()} + {countUnseenNotifications(notifications)}
Logout diff --git a/src/components/notifications/friend-request-accepted.component.tsx b/src/components/notifications/friend-request-accepted.component.tsx index 633d422..bd41de2 100644 --- a/src/components/notifications/friend-request-accepted.component.tsx +++ b/src/components/notifications/friend-request-accepted.component.tsx @@ -1,36 +1,34 @@ import { useEffect } from "react"; -import { eventFetchFriendRequests } from "../../services/friend-request.service"; import { countUnseenNotifications } from "../../utils/count-unseen-notifications"; import Notification from "../../types/notification"; +import { useNotificationStore } from "../../stores/notification.store"; +import { NotificationService, EventName } from "../../services/notification.service"; -export default function FriendRequestReceived() { +export default function FriendRequestAccepted({ notificationService }: { notificationService: NotificationService }) { - function saveReceivedRequest(request: any) { + const { notifications, addNotification } = useNotificationStore(); + + function saveAcceptedRequest(request: any) { const notification: Notification = { - id: request.id, - type: "friend-request-received", - emitterId: request.senderId, - receivedAt: request.requestedAt, - didIAccept: false, - status: "pending-request", + id: crypto.randomUUID(), + type: "friend-request-accepted", + emitterId: request.userId, + receivedAt: new Date().toISOString(), + status: "my-friend-request-accepted", isSeen: false, } - const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); - const updatedNotifications = [notification, ...existingNotifications]; - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - - console.log("on passe ici ?"); - - countUnseenNotifications(); + addNotification(notification); + countUnseenNotifications(notifications); } + + useEffect(() => { - const handleNewFriendRequest = (request: any) => { - saveReceivedRequest(request); + const handleAcceptedFriendRequest = (request: any) => { + saveAcceptedRequest(request); } - - const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + const eventSource = notificationService.eventListener(handleAcceptedFriendRequest, EventName.FRIEND_REQUEST_ACCEPTED); return () => { eventSource.close(); diff --git a/src/components/notifications/friend-request-received.component.tsx b/src/components/notifications/friend-request-received.component.tsx index c590263..249a936 100644 --- a/src/components/notifications/friend-request-received.component.tsx +++ b/src/components/notifications/friend-request-received.component.tsx @@ -1,38 +1,42 @@ import { useEffect } from "react"; -import { eventAcceptedFriendRequest } from "../../services/friend-request.service"; import { countUnseenNotifications } from "../../utils/count-unseen-notifications"; import Notification from "../../types/notification"; +import { useNotificationStore } from "../../stores/notification.store"; +import { NotificationService, EventName } from "../../services/notification.service"; -export default function FriendRequestAccepted() { +export default function FriendRequestReceived({ notificationService }: { notificationService: NotificationService }) { + const { notifications, addNotification } = useNotificationStore(); - function saveAcceptedRequest(request: any) { + function saveReceivedRequest(request: any) { const notification: Notification = { - id: crypto.randomUUID(), - type: "friend-request-accepted", - emitterId: request.userId, - receivedAt: new Date().toISOString(), - status: "my-friend-request-accepted", + id: request.id, + type: "friend-request-received", + emitterId: request.senderId, + receivedAt: request.requestedAt, + didIAccept: false, + status: "pending-request", isSeen: false, - } - const existingNotifications = JSON.parse(localStorage.getItem('notifications') || '[]'); + }; + addNotification(notification); + const existingNotifications = JSON.parse( + localStorage.getItem("notifications") || "[]" + ); const updatedNotifications = [notification, ...existingNotifications]; - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - countUnseenNotifications(); + localStorage.setItem("notifications", JSON.stringify(updatedNotifications)); + countUnseenNotifications(notifications); } - - useEffect(() => { - const handleAcceptedFriendRequest = (request: any) => { - saveAcceptedRequest(request); - } - const eventSource = eventAcceptedFriendRequest(handleAcceptedFriendRequest); + const handleNewFriendRequest = (request: any) => { + saveReceivedRequest(request); + }; + const eventSource = notificationService.eventListener(handleNewFriendRequest, EventName.FRIEND_REQUEST_RECEIVED); return () => { eventSource.close(); }; - }, []) + }, []); - return null -} \ No newline at end of file + return null; +} diff --git a/src/components/notifications/notifications.component.tsx b/src/components/notifications/notifications.component.tsx index 94845aa..017af47 100644 --- a/src/components/notifications/notifications.component.tsx +++ b/src/components/notifications/notifications.component.tsx @@ -1,11 +1,16 @@ import FriendRequestReceived from "./friend-request-accepted.component"; import FriendRequestAccepted from "./friend-request-received.component"; +import { NotificationService } from "../../services/notification.service"; +import { useNotificationStore } from "../../stores/notification.store"; export default function Notifications() { + + const { service } = useNotificationStore(); + return ( <> - - + + - ) -} \ No newline at end of file + ); +} diff --git a/src/index.tsx b/src/index.tsx index dde78d7..4826aeb 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -15,6 +15,7 @@ import ProtectedRoute from "./components/guards/procteded-route.guard"; import GuestRoute from "./components/guards/guest-route.guard"; import { MessagesLoader } from "./components/loaders/messages.loader"; import NotificationListPage from "./pages/notifications.page"; +import { NotificationsLoader } from "./components/loaders/notifications.loader"; const router = createBrowserRouter([ { @@ -23,6 +24,7 @@ const router = createBrowserRouter([ children: [ { element: , + loader: NotificationsLoader, children: [ { index: true, diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index 7a3fe91..6b43a4b 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -2,16 +2,18 @@ import { useLoaderData, useNavigate, useParams } from "react-router-dom"; import { SubmitHandler, useForm } from "react-hook-form"; import { useEffect } from "react"; -import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; -import { FalseMessageService, MessageService } from "../services/message.service"; import { dateFormater } from "../utils/dateFormater"; -import { MessageAdapter } from "../adapters/message.adapter"; -import { BadRequestError } from "../errors/bad-request.error"; + +import { useMessageStore } from "../stores/message.store"; import { useUserStore } from "../stores/user.store"; -// const messageService : MessageAdapter = new FalseMessageService(); -const messageService: MessageAdapter = new MessageService(); +import { MessageService } from "../services/message.service"; +import { MessageAdapter } from "../adapters/message.adapter"; +import { BadRequestError } from "../errors/bad-request.error"; +import Notifications from "../components/notifications/notifications.component"; +import { useNotificationStore } from "../stores/notification.store"; +import { EventName } from "../services/notification.service"; export default function ChatPage() { @@ -28,6 +30,13 @@ export default function ChatPage() { const { messages, setMessages, addMessage, updateErrorLastMessage } = useMessageStore(); const { id } = useUserStore(); + + const { service } = useNotificationStore(); + + + // const messageService : MessageAdapter = new FalseMessageService(notificationService); + const messageService: MessageAdapter = new MessageService(); + const { register, handleSubmit, reset, formState: { errors } } = useForm({ defaultValues: { content: "" @@ -74,7 +83,7 @@ export default function ChatPage() { addMessage(message); }; - const eventSource = messageService.eventFetchMessages(handleNewMessage); + const eventSource = service.eventListener(handleNewMessage, EventName.MESSAGE_RECEIVED); return () => { eventSource.close(); @@ -94,7 +103,8 @@ export default function ChatPage() { {word} ); - } else { + } + else { return {word} ; } }); @@ -117,12 +127,14 @@ export default function ChatPage() {
-
- - -
+
+ + +
-
+ + +
); } \ No newline at end of file diff --git a/src/pages/friend.page.tsx b/src/pages/friend.page.tsx index c98e568..85e0059 100644 --- a/src/pages/friend.page.tsx +++ b/src/pages/friend.page.tsx @@ -1,7 +1,7 @@ import { useEffect, useState } from "react"; import FriendRequestCard from "../components/friend-request-card.component"; -import { eventFetchFriendRequests, fetchFriendRequests } from "../services/friend-request.service"; -import Loader from "../components/loaders/loader.component"; +import { fetchFriendRequests } from "../services/friend-request.service"; +import Loader from "../components/loaders/spinner/loader.component"; import { FriendRequest } from "../types/friend-request"; import AddFriend from "../components/add-friend.component"; @@ -9,18 +9,25 @@ export default function FriendPage() { const [friendRequests, setFriendRequests] = useState([]); const [loading, setLoading] = useState(true); + const removeFriendRequest = (friendRequest: FriendRequest) => { + setFriendRequests((prevRequests) => + prevRequests.filter((request) => request.id !== friendRequest.id) + ); + }; + + useEffect(() => { loadInitialRequests(); const handleNewFriendRequest = (data: any) => { setFriendRequests(prevRequests => {return [data, ...prevRequests];}); }; - - const eventSource = eventFetchFriendRequests(handleNewFriendRequest); - - return () => { - eventSource.close(); - }; + // + // const eventSource = eventFetchFriendRequests(handleNewFriendRequest); + // + // return () => { + // eventSource.close(); + // }; }, []); async function loadInitialRequests() { @@ -45,11 +52,15 @@ export default function FriendPage() { Friends Requests {friendRequests.map((request) => ( - +
+ +
))}
+ +
); } \ No newline at end of file diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 50e2f3f..8f3bc48 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -31,20 +31,22 @@ export default function HomePage() { }; return ( -
-

Home Page

+ <>
-
    - {sortedByDateFriendsList.map((friend) => ( -
  • redirectUserToChatPage(e, friend.userId)} - key={friend.userId} - > - {friend.username} -
  • - ))} -
+

Home Page

+
+
    + {sortedByDateFriendsList.map((friend) => ( +
  • redirectUserToChatPage(e, friend.userId)} + key={friend.userId} + > + {friend.username} +
  • + ))} +
+
-
+ ); } diff --git a/src/pages/notifications.page.tsx b/src/pages/notifications.page.tsx index 934bd88..f706865 100644 --- a/src/pages/notifications.page.tsx +++ b/src/pages/notifications.page.tsx @@ -3,59 +3,61 @@ import { Friend } from "../types/friend"; import { getUserFriends } from "../services/friend.service"; import Notification from "../types/notification"; import { countUnseenNotifications } from "../utils/count-unseen-notifications"; -import { useFriendStore } from "../stores/friend.store"; +import { useNotificationStore } from "../stores/notification.store"; export default function NotificationListPage() { + const { notifications, setNotifications } = useNotificationStore(); + const [friends, setFriends] = useState([]); - const [notifications, setNotifications] = useState([]); useEffect(() => { - const storedNotifications = localStorage.getItem('notifications'); - const parsedNotifications = storedNotifications ? JSON.parse(storedNotifications) : []; - setNotifications(parsedNotifications); - - getUserFriends().then(fetchedFriends => { + getUserFriends().then((fetchedFriends) => { setFriends(fetchedFriends); - - const updatedNotifications = parsedNotifications.map((notification: Notification) => { - if (notification.isSeen == false) { - return { - ...notification, - isSeen: "true" - }; - } - if (notification.status === "my-friend-request-accepted") { - const selectedFriend = fetchedFriends.find( - (friend: Friend) => friend.userId === notification.emitterId - ); - - if (selectedFriend) { + + const updatedNotifications = notifications.map( + (notification: Notification) => { + if (notification.isSeen === false) { return { ...notification, - status: "friend-accepted", - emitterUsername: selectedFriend.username + isSeen: true, }; } - } - if (notification.didIAccept) { - const selectedFriend = fetchedFriends.find( - (friend: Friend) => friend.userId === notification.emitterId - ); - - if (selectedFriend) { - return { - ...notification, - status: "friend-accepted", - emitterUsername: selectedFriend.username - }; + if (notification.status === "my-friend-request-accepted") { + const selectedFriend = fetchedFriends.find( + (friend: Friend) => friend.userId === notification.emitterId + ); + + if (selectedFriend) { + return { + ...notification, + status: "friend-accepted", + emitterUsername: selectedFriend.username, + }; + } + } + if (notification.didIAccept) { + const selectedFriend = fetchedFriends.find( + (friend: Friend) => friend.userId === notification.emitterId + ); + + if (selectedFriend) { + return { + ...notification, + status: "friend-accepted", + emitterUsername: selectedFriend.username, + }; + } } + return notification; } - return notification; - }); + ); setNotifications(updatedNotifications); - localStorage.setItem('notifications', JSON.stringify(updatedNotifications)); - countUnseenNotifications(); + localStorage.setItem( + "notifications", + JSON.stringify(updatedNotifications) + ); + countUnseenNotifications(notifications); }); }, []); @@ -81,13 +83,17 @@ export default function NotificationListPage() { : "bg-blue-100 border-blue-200 p-4 rounded-lg border" } > - {notification.status === 'friend-accepted' && ( - {notification.emitterUsername}'s request approved by you + {notification.status === "friend-accepted" && ( + + {notification.emitterUsername}'s request approved by you + )} - {notification.status === 'my-friend-request-accepted' && ( - {notification.emitterId} accepted your friend request + {notification.status === "my-friend-request-accepted" && ( + + {notification.emitterId} accepted your friend request + )} - {notification.status === 'pending-request' && ( + {notification.status === "pending-request" && ( {notification.emitterId} sent you a friend request )}
@@ -96,4 +102,4 @@ export default function NotificationListPage() {
); -} \ No newline at end of file +} diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts index 9707f76..522fad6 100644 --- a/src/services/auth.service.ts +++ b/src/services/auth.service.ts @@ -38,6 +38,7 @@ export async function registerUser(data: UserDTO) { } export async function logoutUser() { + const response = await fetch( `${process.env.REACT_APP_API_BASE_URL}/auth/logout`, { @@ -49,6 +50,7 @@ export async function logoutUser() { const { message } = await response.json(); throw new Error(message); } + localStorage.clear(); } export async function getCurrentUser(): Promise { diff --git a/src/services/friend-request.service.ts b/src/services/friend-request.service.ts index f3d52e4..b55eb54 100644 --- a/src/services/friend-request.service.ts +++ b/src/services/friend-request.service.ts @@ -32,27 +32,3 @@ export const acceptRequest = async (requestId: string) => { credentials: 'include', }) } - -export const eventFetchFriendRequests = (onRequestReceived: (data: any) => void) => { - const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) - eventSource.addEventListener('friend-request-received', (event) => { - const data = JSON.parse(event.data); - onRequestReceived(data); - }) - eventSource.onerror = (error) => { - eventSource.close(); - }; - return eventSource; -} - -export const eventAcceptedFriendRequest = (onRequestAccepted: (data: any) => void) => { - const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) - eventSource.addEventListener('friend-request-accepted', (event) => { - const data = JSON.parse(event.data); - onRequestAccepted(data); - }) - eventSource.onerror = (error) => { - eventSource.close(); - }; - return eventSource; -} \ No newline at end of file diff --git a/src/services/message.service.ts b/src/services/message.service.ts index e961195..61ce180 100644 --- a/src/services/message.service.ts +++ b/src/services/message.service.ts @@ -3,7 +3,9 @@ import { MessageAdapter } from "../adapters/message.adapter"; import { BadRequestError } from "../errors/bad-request.error"; export class MessageService implements MessageAdapter { - sendMessage = async (message: Message): Promise => { + sendMessage = async (message: Message): Promise => { + console.log(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`) + console.log({receiverId: message.receiverId, content: message.content,}) const response = await fetch(`${process.env.REACT_APP_API_BASE_URL}/chat/${message.id}/send`, { method: 'POST', headers: { @@ -15,7 +17,8 @@ export class MessageService implements MessageAdapter { content: message.content, }), }) - //TODO: Gérer plusieurs erreurs dont erreur connexion + console.log(response) + if (response.status === 400) { throw new BadRequestError("You are not friend with this user"); } @@ -39,19 +42,6 @@ export class MessageService implements MessageAdapter { } return await response.json() } - - eventFetchMessages = (onMessageReceived: (data: Message) => void) => { - const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) - eventSource.addEventListener('message-received', (event) => { - const data = JSON.parse(event.data); - onMessageReceived(data); - }) - - eventSource.onerror = (error) => { - eventSource.close(); - }; - return eventSource; - } } @@ -100,17 +90,4 @@ export class FalseMessageService implements MessageAdapter { } return await response.json() } - - eventFetchMessages = (onMessageReceived: (data: Message) => void) => { - const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) - eventSource.addEventListener('message-received', (event) => { - const data = JSON.parse(event.data); - onMessageReceived(data); - }) - - eventSource.onerror = (error) => { - eventSource.close(); - }; - return eventSource; - } } diff --git a/src/services/notification.service.ts b/src/services/notification.service.ts new file mode 100644 index 0000000..e0e1ec4 --- /dev/null +++ b/src/services/notification.service.ts @@ -0,0 +1,22 @@ +import Message from "../types/message"; + +export enum EventName { + FRIEND_REQUEST_RECEIVED = 'friend-request-received', + FRIEND_REQUEST_ACCEPTED = 'friend-request-accepted', + MESSAGE_RECEIVED = 'message-received', +} + +export class NotificationService { + + eventListener = (onElementReceived: (data: any) => void, eventName: EventName) => { + const eventSource = new EventSource(`${process.env.REACT_APP_API_BASE_URL}/notifications`, { withCredentials: true }) + eventSource.addEventListener(eventName, (event) => { + const data = JSON.parse(event.data); + onElementReceived(data); + }) + eventSource.onerror = (error) => { + eventSource.close(); + }; + return eventSource; + } +} \ No newline at end of file diff --git a/src/stores/notification.store.ts b/src/stores/notification.store.ts index 883f48a..4431037 100644 --- a/src/stores/notification.store.ts +++ b/src/stores/notification.store.ts @@ -1,12 +1,30 @@ import { create } from "zustand/react"; import Notification from "../types/notification"; +import { EventName, NotificationService } from "../services/notification.service"; + +const notificationService = new NotificationService(); type NotificationsStore = { notifications: Notification[]; + service: NotificationService; + setNotifications: (notifications: Notification[]) => void; addNotification: (notification: Notification) => void; -} -export const useNotificationStore = create((set) => ({ + getMessagesNotifications: () => Notification[]; + getFriendRequestReceivedNotifications: () => Notification[]; + getFriendRequestAcceptedNotifications: () => Notification[]; +}; + +export const useNotificationStore = create((set, get) => ({ notifications: [], - addNotification: (notification: Notification) => set((state) => ({ notifications: [notification, ...state.notifications] })), -})) \ No newline at end of file + service: notificationService, + setNotifications: (notifications: Notification[]) => set({ notifications }), + addNotification: (notification: Notification) => + set((state) => ({ notifications: [notification, ...state.notifications] })), + getMessagesNotifications: () => + get().notifications.filter((notification) => notification.type === EventName.MESSAGE_RECEIVED), + getFriendRequestReceivedNotifications: () => + get().notifications.filter((notification) => notification.type === EventName.FRIEND_REQUEST_RECEIVED), + getFriendRequestAcceptedNotifications: () => + get().notifications.filter((notification) => notification.type === EventName.FRIEND_REQUEST_ACCEPTED), +})); diff --git a/src/types/notification.ts b/src/types/notification.ts index 8e95722..502d6a6 100644 --- a/src/types/notification.ts +++ b/src/types/notification.ts @@ -6,5 +6,5 @@ export default interface Notification { receivedAt: string; didIAccept?: boolean; status?: string; - isSeen: false; + isSeen: boolean; } \ No newline at end of file diff --git a/src/utils/count-unseen-notifications.tsx b/src/utils/count-unseen-notifications.tsx index 526af2f..6a27df0 100644 --- a/src/utils/count-unseen-notifications.tsx +++ b/src/utils/count-unseen-notifications.tsx @@ -1,19 +1,15 @@ import Notification from "../types/notification"; -export function countUnseenNotifications() { - console.log("count"); - - const storedNotifications = localStorage.getItem('notifications'); - const parsedNotifications = storedNotifications ? JSON.parse(storedNotifications) : []; +export function countUnseenNotifications(notifications: Notification[]) { + // console.log("count"); let count = 0; - parsedNotifications.map((notification: Notification) => { + notifications.map((notification: Notification) => { if (notification.isSeen == false || notification.didIAccept == false) { count = count + 1; } }); - console.log(count); + // console.log(count); - localStorage.setItem('unseenNotif', JSON.stringify(count)); return count.toString(); } \ No newline at end of file From dad30704d2f5fd1ec95a7fcafcc39a229db1bc30 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 22:51:58 +0100 Subject: [PATCH 095/118] feat : add friends home and chat styles --- .../cards/friend-placeholder.card.css | 5 + .../cards/friend-placeholder.card.tsx | 5 + src/components/cards/friend.card.style.css | 8 ++ src/components/cards/friend.card.tsx | 10 ++ src/components/cards/message.card.tsx | 31 +++++ src/index.css | 21 +++ src/pages/chat.page.tsx | 130 +++++++++++++----- src/pages/home.page.tsx | 31 +++-- src/stores/friend.store.ts | 5 + 9 files changed, 196 insertions(+), 50 deletions(-) create mode 100644 src/components/cards/friend-placeholder.card.css create mode 100644 src/components/cards/friend-placeholder.card.tsx create mode 100644 src/components/cards/friend.card.style.css create mode 100644 src/components/cards/friend.card.tsx create mode 100644 src/components/cards/message.card.tsx diff --git a/src/components/cards/friend-placeholder.card.css b/src/components/cards/friend-placeholder.card.css new file mode 100644 index 0000000..d0ce5a5 --- /dev/null +++ b/src/components/cards/friend-placeholder.card.css @@ -0,0 +1,5 @@ +.friend-card-placeholder { + @apply border-4 border-zinc-400 aspect-video rounded-[5%/40%] bg-slate-50 shadow-[inset_0px_-64px_32px_rgba(0,0,0,0.2)] cursor-not-allowed; + @apply bg-gradient-to-b from-zinc-300 via-zinc-300 to-zinc-300; + @apply bg-[length:7px_7px]; +} diff --git a/src/components/cards/friend-placeholder.card.tsx b/src/components/cards/friend-placeholder.card.tsx new file mode 100644 index 0000000..628fffe --- /dev/null +++ b/src/components/cards/friend-placeholder.card.tsx @@ -0,0 +1,5 @@ +import "./friend-placeholder.card.css"; + +export default function FriendCardPlaceholder() { + return
; +} diff --git a/src/components/cards/friend.card.style.css b/src/components/cards/friend.card.style.css new file mode 100644 index 0000000..577878c --- /dev/null +++ b/src/components/cards/friend.card.style.css @@ -0,0 +1,8 @@ +.friend-card { + @apply flex justify-center items-center font-bold text-2xl uppercase; + @apply cursor-pointer border-4 border-zinc-400 aspect-video rounded-[5%/40%] bg-slate-50 shadow-[inset_0px_-64px_32px_rgba(0,0,0,0.2)] hover:scale-105 transition-all duration-300; +} + +.friend-card:hover { + @apply ring ring-cyan-500 border-cyan-300; +} diff --git a/src/components/cards/friend.card.tsx b/src/components/cards/friend.card.tsx new file mode 100644 index 0000000..b407c42 --- /dev/null +++ b/src/components/cards/friend.card.tsx @@ -0,0 +1,10 @@ +import { Friend } from "../../types/friend"; +import "./friend.card.style.css"; + +interface FriendCardProps { + friend: Friend; +} + +export default function FriendCard({ friend }: FriendCardProps) { + return
{friend.username}
; +} diff --git a/src/components/cards/message.card.tsx b/src/components/cards/message.card.tsx new file mode 100644 index 0000000..1c6b93f --- /dev/null +++ b/src/components/cards/message.card.tsx @@ -0,0 +1,31 @@ +import Message from "../../types/message"; +import { dateFormater } from "../../utils/dateFormater"; + +interface MessageCardProps { + message: Message; + isSender?: boolean; +} + +export default function MessageCard({ + message, + isSender = true, +}: MessageCardProps) { + return ( +
+
+

{message.content}

+
+

+ {dateFormater(message.sendAt)} +

+
+ ); +} diff --git a/src/index.css b/src/index.css index a3f5f6e..d6e47ae 100644 --- a/src/index.css +++ b/src/index.css @@ -97,3 +97,24 @@ body { h1 { @apply text-6xl font-bold; } + +main { + @apply max-w-[1440px] w-full h-full m-auto; +} + +.chat-wrapper::-webkit-scrollbar { + width: 10px; +} + +.chat-wrapper::-webkit-scrollbar-track { + background: #f1f1f1; +} + +.chat-wrapper::-webkit-scrollbar-thumb { + @apply bg-slate-200; + border-radius: 999px; +} + +.chat-wrapper::-webkit-scrollbar-thumb:hover { + @apply bg-slate-300; +} diff --git a/src/pages/chat.page.tsx b/src/pages/chat.page.tsx index b2dc8e0..1720629 100644 --- a/src/pages/chat.page.tsx +++ b/src/pages/chat.page.tsx @@ -1,56 +1,82 @@ -import {useNavigate, useParams} from "react-router-dom"; +import { useNavigate, useParams } from "react-router-dom"; import { SubmitHandler, useForm } from "react-hook-form"; -import { useEffect } from "react"; +import { useEffect, useRef, useState } from "react"; import { useMessageStore } from "../stores/message.store"; import Message from "../types/message"; -import { sendMessage, fetchMessages, eventFetchMessages } from "../services/message.service"; +import { + sendMessage, + fetchMessages, + eventFetchMessages, +} from "../services/message.service"; import MessagesLoader from "../components/loaders/messages.loader"; -import { dateFormater } from "../utils/dateFormater"; +import Button from "../components/buttons/button"; +import MessageCard from "../components/cards/message.card"; +import { useFriendStore } from "../stores/friend.store"; +import { Friend } from "../types/friend"; -export default function ChatPage() { +const MAX_MESSAGE_LENGTH = 255; +export default function ChatPage() { type FormInputs = { content: string; - } + }; const navigate = useNavigate(); const { receiverId } = useParams(); - const { messages, setMessages, addMessage, updateLastMessage } = useMessageStore(); + const { messages, setMessages, addMessage, updateLastMessage } = + useMessageStore(); + + const { getFriendById } = useFriendStore(); + + const [currentFriend, setCurrentFriend] = useState(); - const { register, handleSubmit, reset, formState: { errors } } = useForm({ + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ defaultValues: { - content: "" - } - }) + content: "", + }, + }); const onSubmit: SubmitHandler = async (input) => { if (!receiverId) return; - const message: Message = {id: "", content: input.content, receiverId: receiverId, emitterId: "", sendAt: (new Date()).toISOString()}; + const message: Message = { + id: "", + content: input.content, + receiverId: receiverId, + emitterId: "", + sendAt: new Date().toISOString(), + }; addMessage(message); try { await sendMessage(message); - } - catch (error) { - navigate('/'); + } catch (error) { + navigate("/"); } const messages = await fetchMessages(receiverId); setMessages(messages); reset(); - } + }; useEffect(() => { if (!receiverId) return; + + setCurrentFriend(getFriendById(receiverId)); + const loadMessages = async (): Promise => { try { const messages = await fetchMessages(receiverId); setMessages(messages); } catch (error) { - navigate('/chats'); + navigate("/chats"); } }; loadMessages(); @@ -66,32 +92,64 @@ export default function ChatPage() { }; }, [receiverId]); + const messagesBox = useRef(null); + function scrollToBottom() { + if (messagesBox.current) { + messagesBox.current.scrollTop = messagesBox.current.scrollHeight; + } + } + useEffect(() => { + scrollToBottom(); + }, [messages]); + + const [charactersLeft, setCharactersLeft] = useState(MAX_MESSAGE_LENGTH); + return ( - -
-
+
+ {currentFriend && ( +

{currentFriend.username} Chat

+ )} +
{messages.map((message, index) => ( - message.receiverId === receiverId ? ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) : ( -
- {`${message.content} : ${dateFormater(message.sendAt)}`} -
- ) + ))}
- -
- - + +
+ +
- + {charactersLeft !== MAX_MESSAGE_LENGTH && ( +

+ {charactersLeft} characters left +

+ )}
); -} \ No newline at end of file +} diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 50e2f3f..2ab536a 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -2,6 +2,8 @@ import { useEffect } from "react"; import { getUserFriends } from "../services/friend.service"; import { useNavigate } from "react-router-dom"; import { useFriendStore } from "../stores/friend.store"; +import FriendCard from "../components/cards/friend.card"; +import FriendCardPlaceholder from "../components/cards/friend-placeholder.card"; export default function HomePage() { const { friends, setFriends } = useFriendStore(); @@ -31,20 +33,21 @@ export default function HomePage() { }; return ( -
-

Home Page

-
-
    - {sortedByDateFriendsList.map((friend) => ( -
  • redirectUserToChatPage(e, friend.userId)} - key={friend.userId} - > - {friend.username} -
  • +
    +
      + {sortedByDateFriendsList.map((friend) => ( +
    • redirectUserToChatPage(e, friend.userId)} + key={friend.userId} + > + +
    • + ))} + {friends.length < 15 && + Array.from({ length: 15 - friends.length }).map((_, index) => ( + ))} -
    -
-
+ + ); } diff --git a/src/stores/friend.store.ts b/src/stores/friend.store.ts index f118f32..65f203f 100644 --- a/src/stores/friend.store.ts +++ b/src/stores/friend.store.ts @@ -9,6 +9,7 @@ type Actions = { setFriends: (friends: Friend[]) => void; addFriend: (friend: Friend) => void; clearFriends: () => void; + getFriendById: (userId: string) => Friend | undefined; }; export const useFriendStore = create((set) => ({ @@ -17,4 +18,8 @@ export const useFriendStore = create((set) => ({ addFriend: (friend: Friend) => set((state) => ({ friends: [...state.friends, friend] })), clearFriends: () => set({ friends: [] }), + getFriendById: (userId: string): Friend | undefined => + useFriendStore + .getState() + .friends.find((friend: Friend) => friend.userId === userId), })); From 50681c48e1ce41a32eed0b6bce4283d1a047eb90 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 23:20:48 +0100 Subject: [PATCH 096/118] feat : footer responsive --- src/components/navigation.tsx | 20 +++++++++++++------- src/index.css | 4 ++-- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index d1bfde8..4e38fe4 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -23,19 +23,25 @@ export default function Navigation() { return (
-
- navigate("/")}> -
Chats
+
+ navigate("/")}> +
Chats
- navigate("/friends")}> + navigate("/friends")} + >
-
- handleNotifications()}> +
+ handleNotifications()} + > - handleLogout()}> + handleLogout()}>
diff --git a/src/index.css b/src/index.css index d6e47ae..abf5b13 100644 --- a/src/index.css +++ b/src/index.css @@ -4,7 +4,7 @@ @layer components { .btn { - @apply relative px-8 py-1 rounded-full overflow-hidden transition-all duration-300 outline-none font-semibold; + @apply relative md:px-8 md:py-1 px-2 rounded-full overflow-hidden transition-all duration-300 outline-none font-semibold; } .btn:hover, @@ -95,7 +95,7 @@ body { } h1 { - @apply text-6xl font-bold; + @apply text-5xl font-bold sm:text-6xl; } main { From 2b3e1e37dbe8906fdf7d29ba33acce5cf00d9f77 Mon Sep 17 00:00:00 2001 From: Val Date: Thu, 7 Nov 2024 23:53:22 +0100 Subject: [PATCH 097/118] feat : responsive home --- src/components/cards/message.card.tsx | 6 +++++- src/index.css | 8 ++++---- src/pages/home.page.tsx | 8 ++++---- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/cards/message.card.tsx b/src/components/cards/message.card.tsx index 1c6b93f..479929c 100644 --- a/src/components/cards/message.card.tsx +++ b/src/components/cards/message.card.tsx @@ -23,7 +23,11 @@ export default function MessageCard({ >

{message.content}

-

+

{dateFormater(message.sendAt)}

diff --git a/src/index.css b/src/index.css index abf5b13..99120b4 100644 --- a/src/index.css +++ b/src/index.css @@ -102,19 +102,19 @@ main { @apply max-w-[1440px] w-full h-full m-auto; } -.chat-wrapper::-webkit-scrollbar { +::-webkit-scrollbar { width: 10px; } -.chat-wrapper::-webkit-scrollbar-track { +::-webkit-scrollbar-track { background: #f1f1f1; } -.chat-wrapper::-webkit-scrollbar-thumb { +::-webkit-scrollbar-thumb { @apply bg-slate-200; border-radius: 999px; } -.chat-wrapper::-webkit-scrollbar-thumb:hover { +::-webkit-scrollbar-thumb:hover { @apply bg-slate-300; } diff --git a/src/pages/home.page.tsx b/src/pages/home.page.tsx index 2ab536a..92beefc 100644 --- a/src/pages/home.page.tsx +++ b/src/pages/home.page.tsx @@ -33,8 +33,8 @@ export default function HomePage() { }; return ( -
-