diff --git a/frontend/index.html b/frontend/index.html
index 59f7892..2182ae4 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -37,6 +37,7 @@
⭕ Tic-Tac-Toe
▮•▮ Ping Pong
🏆 Tournaments
+ 😎 Friends
🚪 Logout
diff --git a/frontend/src/auth.ts b/frontend/src/auth.ts
index 5c97eab..63491fc 100644
--- a/frontend/src/auth.ts
+++ b/frontend/src/auth.ts
@@ -1,7 +1,7 @@
import { showError } from "@app/toast";
import client from "@app/api";
import cookie from "js-cookie";
-import { ensureWindowState, isNullish } from "@app/utils";
+import { ensureWindowState, isNullish, updateFriendsList } from "@app/utils";
import { handleRoute, navigateTo } from "./routing";
cookie.remove("pkce");
@@ -105,10 +105,11 @@ if (!window.__state._headerProfile) {
window.__state._reloadOnAuthChange ??= false;
if (!window.__state._reloadOnAuthChange) {
- document.addEventListener("ft:userChange", () => {
+ document.addEventListener("ft:userChange", async () => {
// if the last forced auth change is less than 1000 sec old -> we do nothing
if (Date.now() - (window.__state.lastAuthChange ?? Date.now()) < 1000)
return;
+ await updateFriendsList();
handleRoute();
});
window.__state._reloadOnAuthChange = true;
diff --git a/frontend/src/chat/chat.ts b/frontend/src/chat/chat.ts
index f3e2aa2..f52ce0b 100644
--- a/frontend/src/chat/chat.ts
+++ b/frontend/src/chat/chat.ts
@@ -39,6 +39,7 @@ let keysPressed: Record = {};
declare module "ft_state" {
interface State {
chatSock?: Socket;
+ friendList: { id: string; name: string }[];
}
}
@@ -82,18 +83,20 @@ const sendButton = document.getElementById("b-send") as HTMLButtonElement;
const sendtextbox = document.getElementById(
"t-chat-window",
) as HTMLButtonElement;
-const systemWindow = document.getElementById("chat-system-box") as HTMLDivElement;
+const systemWindow = document.getElementById(
+ "chat-system-box",
+) as HTMLDivElement;
function chatKeyToggle() {
let anti_flicker_control = false;
- const chat_hide_key = 'escape';
- const chat_display_key = 'f2';
- const home_display_key = 'f8';
+ const chat_hide_key = "escape";
+ const chat_display_key = "f2";
+ const home_display_key = "f8";
document.addEventListener("keydown", (event) => {
if (event.repeat && keysPressed[chat_hide_key] === true) {
anti_flicker_control = true;
- return ;
- };
+ return;
+ }
keysPressed[event.key.toLowerCase()] = true;
});
document.addEventListener("keyup", (event) => {
@@ -102,34 +105,33 @@ function chatKeyToggle() {
anti_flicker_control = false;
}
});
- setInterval( () => {
- if(keysPressed[chat_hide_key] === true) {
- overlay.classList.remove("opacity-60");
- chatBox.classList.add("hidden");
- chatMessageIn?.classList.add("hidden");
- chatMessageIn!.textContent = '';
- profilList?.classList.add("hidden");
- windowStateHidden();
+ setInterval(() => {
+ if (keysPressed[chat_hide_key] === true) {
+ overlay.classList.remove("opacity-60");
+ chatBox.classList.add("hidden");
+ chatMessageIn?.classList.add("hidden");
+ chatMessageIn!.textContent = "";
+ profilList?.classList.add("hidden");
+ windowStateHidden();
}
if (keysPressed[chat_display_key] === true) {
- anti_flicker_control = false;
- chatBox.classList.remove("hidden");
- overlay.classList.add("opacity-60");
- chatMessageIn?.classList.add("hidden");
- chatMessageIn!.textContent = '';
- let socket = window.__state.chatSock;
- if (!socket) return;
- connected(socket);
- sendtextbox.focus();
- windowStateVisable();
-
+ anti_flicker_control = false;
+ chatBox.classList.remove("hidden");
+ overlay.classList.add("opacity-60");
+ chatMessageIn?.classList.add("hidden");
+ chatMessageIn!.textContent = "";
+ let socket = window.__state.chatSock;
+ if (!socket) return;
+ connected(socket);
+ sendtextbox.focus();
+ windowStateVisable();
}
- if (keysPressed[home_display_key] === true) {
- navigateTo('/app/');
+ if (keysPressed[home_display_key] === true) {
+ navigateTo("/app/");
quitChat();
}
- }, 1000/10);
-};
+ }, 1000 / 10);
+}
function initChatSocket() {
let socket = getSocket();
@@ -152,9 +154,10 @@ function initChatSocket() {
!profilList ||
!sendButton ||
!sendtextbox ||
- !systemWindow
- ) return showError("fatal error");
-
+ !systemWindow
+ )
+ return showError("fatal error");
+
// Listen for the 'connect' event
socket.on("connect", async () => {
await waitSocketConnected(socket);
@@ -202,10 +205,10 @@ function initChatSocket() {
if (socket) {
connected(socket);
}
-
+
if (chatWindow && data.message.destination === "") {
chatMessageIn?.classList.remove("hidden");
- chatMessageIn!.textContent = '🔵';
+ chatMessageIn!.textContent = "🔵";
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
@@ -214,7 +217,7 @@ function initChatSocket() {
if (chatWindow && data.message.destination === "privateMsg") {
chatMessageIn?.classList.remove("hidden");
- chatMessageIn!.textContent = '🔴';
+ chatMessageIn!.textContent = "🔴";
const messageElement = document.createElement("div-private");
messageElement.textContent = `🔒${data.message.user}: ${data.message.text}`;
chatWindow.appendChild(messageElement);
@@ -223,7 +226,7 @@ function initChatSocket() {
if (chatWindow && data.message.destination === "inviteMsg") {
chatMessageIn?.classList.remove("hidden");
- chatMessageIn!.textContent = '🟢';
+ chatMessageIn!.textContent = "🟢";
const messageElement = document.createElement("div-private");
const chatWindow = document.getElementById(
"t-chatbox",
@@ -236,14 +239,13 @@ function initChatSocket() {
if (systemWindow && data.message.destination === "system-info") {
const messageElement = document.createElement("div");
messageElement.textContent = `${data.message.user}: ${data.message.text}`;
-
+
// keep only last 10
while (systemWindow.children.length > MAX_SYSTEM_MESSAGES) {
systemWindow.removeChild(systemWindow.firstChild!);
}
systemWindow.appendChild(messageElement);
systemWindow.lastElementChild?.scrollIntoView({ block: "end" });
-
}
});
@@ -278,9 +280,9 @@ function initChatSocket() {
if (blockUserBtn) {
let message = "";
if (data.userState === "block") {
- (message = "un-block");
+ message = "un-block";
} else {
- (message = "block");
+ message = "block";
}
blockUserBtn.textContent = message;
}
@@ -294,17 +296,15 @@ function initChatSocket() {
const htmlBaliseRegex = /]*>[\s\S]*?<\/a>/;
const htmlBaliseMatch = message.match(htmlBaliseRegex);
- if (htmlBaliseMatch)
- addInviteMessage(message);
- else
- addMessage(message);
+ if (htmlBaliseMatch) addInviteMessage(message);
+ else addMessage(message);
});
//receives broadcast of the next GAME
socket.on("nextGame", (message: string) => {
openMessagePopup(message);
});
-
+
//receives broadcast of the next GAME
socket.on("tourStatus", (message: string) => {
openMessagePopup(message);
@@ -391,20 +391,20 @@ sendButton?.addEventListener("click", () => {
}
break;
- case "@pong":
+ case "@pong":
if (msgCommand[1] === "") {
navigateTo("/app/pong/games");
quitChat();
- }
+ }
break;
- case "@ttt":
+ case "@ttt":
if (msgCommand[1] === "") {
navigateTo("/app/ttt/games");
quitChat();
- }
+ }
break;
-
+
case "@guest":
if (!userId) {
return;
@@ -505,7 +505,6 @@ clearText?.addEventListener("click", () => {
bquit?.addEventListener("click", () => {
quitChat();
-
});
myGames?.addEventListener("click", () => {
@@ -527,7 +526,7 @@ sendtextbox.addEventListener("keydown", (event) => {
}
});
-chatButton!.addEventListener("click",() => {
+chatButton!.addEventListener("click", () => {
if (chatBox.classList.contains("hidden")) {
chatBox.classList.toggle("hidden");
overlay.classList.add("opacity-60");
@@ -536,14 +535,14 @@ chatButton!.addEventListener("click",() => {
if (!socket) return;
connected(socket);
chatMessageIn?.classList.add("hidden");
- chatMessageIn!.textContent = '';
- sendtextbox.focus();
+ chatMessageIn!.textContent = "";
+ sendtextbox.focus();
} else {
chatBox.classList.toggle("hidden");
overlay.classList.remove("opacity-60");
windowStateHidden();
chatMessageIn?.classList.add("hidden");
- chatMessageIn!.textContent = '';
+ chatMessageIn!.textContent = "";
}
});
diff --git a/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts b/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts
index c804154..8ca45ec 100644
--- a/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts
+++ b/frontend/src/chat/chatHelperFunctions/actionBtnFriend.ts
@@ -1,5 +1,8 @@
+import client from "@app/api";
import type { ClientProfil } from "../types_front";
import { Socket } from "socket.io-client";
+import { showError, showSuccess } from "@app/toast";
+import { getFriendList, updateFriendsList } from "@app/utils";
/**
* function listens for a click on the TTT game History button
@@ -8,20 +11,25 @@ import { Socket } from "socket.io-client";
**/
export function actionBtnFriend(profile: ClientProfil, senderSocket: Socket) {
- setTimeout(() => {
- const friend = document.querySelector("#btn-friend");
- friend?.addEventListener("click", () => {
-
- if (friend.textContent = "friend") {
- friend.textContent = "not-friend"
- console.log('friend');
- }
- else {
- friend.textContent = "not-friend"
- console.log('Not a friend');
-
- }
-
- });
- }, 0)
-};
\ No newline at end of file
+ setTimeout(() => {
+ const friend = document.querySelector("#btn-friend");
+ friend?.addEventListener("click", async () => {
+ let friendList = getFriendList();
+ if (!friendList.some(v => v.id === profile.userID!)) {
+ let req = await client.addFriend({ user: profile.userID! });
+ if (req.kind === 'success')
+ showSuccess('Successfully added a new Friend')
+ else
+ showError('Failed to add a new Friend');
+ }
+ else {
+ let req = await client.removeFriend({ user: profile.userID! });
+ if (req.kind === 'success')
+ showSuccess('Successfully removed a Friend')
+ else
+ showError('Failed to remove a Friend');
+ }
+ await updateFriendsList();
+ });
+ }, 0)
+};
diff --git a/frontend/src/pages/friendList/friendList.html b/frontend/src/pages/friendList/friendList.html
new file mode 100644
index 0000000..68f64db
--- /dev/null
+++ b/frontend/src/pages/friendList/friendList.html
@@ -0,0 +1,9 @@
+
diff --git a/frontend/src/pages/friendList/friendList.ts b/frontend/src/pages/friendList/friendList.ts
new file mode 100644
index 0000000..83fb2df
--- /dev/null
+++ b/frontend/src/pages/friendList/friendList.ts
@@ -0,0 +1,41 @@
+import { addRoute, navigateTo, setTitle, type RouteHandlerParams, type RouteHandlerReturn } from "@app/routing";
+import page from './friendList.html?raw';
+import { getFriendList, isNullish, updateFriendsList } from "@app/utils";
+import client from "@app/api";
+import { updateUser } from "@app/auth";
+import { showError } from "@app/toast";
+
+
+async function friends(_url: string, args: RouteHandlerParams): Promise {
+ setTitle("Tic Tac Toe Games");
+ let user = await updateUser();
+ if (isNullish(user)) {
+ return { html: ' You aren\'t logged in ', postInsert: () => { showError("You must be logged in !"); navigateTo("/") } };
+ }
+ await updateFriendsList();
+ let friendList = getFriendList();
+ friendList.sort();
+
+ let friendsElem = friendList.map(g => {
+ let e = document.createElement('div');
+ e.className = 'grid grid-cols-[1fr_auto_1fr] items-center bg-zinc-800 rounded-lg px-4 py-3';
+
+ e.innerHTML = `
+ ${g.name}
+ TTT Games
+ Pong Games
+ `;
+ return e;
+ }).filter(v => !isNullish(v));
+
+ return {
+ html: page, postInsert: async (app) => {
+ if (!app) return;
+ const friendsBox = app.querySelector("#friendList");
+ if (!friendsBox) return;
+ friendsElem.forEach(c => friendsBox.appendChild(c));
+ }
+ };
+}
+
+addRoute('/friends', friends);
diff --git a/frontend/src/pages/index.ts b/frontend/src/pages/index.ts
index cd6c41d..aef2743 100644
--- a/frontend/src/pages/index.ts
+++ b/frontend/src/pages/index.ts
@@ -1,6 +1,5 @@
import { setTitle, handleRoute } from '@app/routing';
import './root/root.ts'
-import '../chat/chat.ts'
import './pong/pong.ts'
import './login/login.ts'
import './signin/signin.ts'
@@ -10,6 +9,7 @@ import './logout/logout.ts'
import './pongHistory/pongHistory.ts'
import './tttHistory/tttHistory.ts'
import './tourHistory/tourHistory.ts'
+import './friendList/friendList.ts'
// ---- Initial load ----
setTitle("");
diff --git a/frontend/src/utils.ts b/frontend/src/utils.ts
index 80a3888..6229799 100644
--- a/frontend/src/utils.ts
+++ b/frontend/src/utils.ts
@@ -1,9 +1,11 @@
+import client from "./api";
+
export function escapeHTML(str: string): string {
const p = document.createElement("p");
p.appendChild(document.createTextNode(str));
return p.innerHTML;
}
-export function isNullish(v: T | undefined | null): v is (null | undefined) {
+export function isNullish(v: T | undefined | null): v is null | undefined {
return v === null || v === undefined;
}
@@ -11,3 +13,22 @@ export function isNullish(v: T | undefined | null): v is (null | undefined) {
export function ensureWindowState() {
window.__state = window.__state ?? {};
}
+
+export async function updateFriendsList() {
+ window.__state = window.__state ?? {};
+ window.__state.friendList ??= [];
+
+ try {
+ let req = await client.listFriend();
+ if (req.kind === "success") {
+ window.__state.friendList = req.payload.friends;
+ }
+ } catch (e: unknown) { }
+}
+
+export function getFriendList() {
+ ensureWindowState();
+ window.__state.friendList ??= [];
+
+ return window.__state.friendList;
+}