From 909e288bec20b5362e999821c5d82a10c2b86a1a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 21 Jul 2024 15:45:13 -0300 Subject: [PATCH 01/21] feat: background on profile page for other users profile --- src/renderer/src/helpers.ts | 12 +++++++++ src/renderer/src/hooks/use-user-details.ts | 13 +++------- src/renderer/src/pages/user/user-content.tsx | 26 +++++++++++++++----- 3 files changed, 36 insertions(+), 15 deletions(-) diff --git a/src/renderer/src/helpers.ts b/src/renderer/src/helpers.ts index d37612d4..182aef25 100644 --- a/src/renderer/src/helpers.ts +++ b/src/renderer/src/helpers.ts @@ -1,6 +1,7 @@ import type { GameShop } from "@types"; import Color from "color"; +import { average } from "color.js"; export const steamUrlBuilder = { library: (objectID: string) => @@ -45,3 +46,14 @@ export const buildGameDetailsPath = ( export const darkenColor = (color: string, amount: number, alpha: number = 1) => new Color(color).darken(amount).alpha(alpha).toString(); + +export const profileBackgroundFromProfileImage = async ( + profileImageUrl: string +) => { + const output = await average(profileImageUrl, { + amount: 1, + format: "hex", + }); + + return `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8, 0.7)})`; +}; diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index a0da950a..80ef8b4c 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -1,6 +1,4 @@ import { useCallback } from "react"; -import { average } from "color.js"; - import { useAppDispatch, useAppSelector } from "./redux"; import { setProfileBackground, @@ -9,7 +7,7 @@ import { setFriendsModalVisible, setFriendsModalHidden, } from "@renderer/features"; -import { darkenColor } from "@renderer/helpers"; +import { profileBackgroundFromProfileImage } from "@renderer/helpers"; import { FriendRequestAction, UserDetails } from "@types"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; @@ -42,12 +40,9 @@ export function useUserDetails() { dispatch(setUserDetails(userDetails)); if (userDetails.profileImageUrl) { - const output = await average(userDetails.profileImageUrl, { - amount: 1, - format: "hex", - }); - - const profileBackground = `linear-gradient(135deg, ${darkenColor(output as string, 0.6)}, ${darkenColor(output as string, 0.8, 0.7)})`; + const profileBackground = await profileBackgroundFromProfileImage( + userDetails.profileImageUrl + ); dispatch(setProfileBackground(profileBackground)); window.localStorage.setItem( diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index e70335d8..cf332405 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -12,7 +12,11 @@ import { useUserDetails, } from "@renderer/hooks"; import { useNavigate } from "react-router-dom"; -import { buildGameDetailsPath, steamUrlBuilder } from "@renderer/helpers"; +import { + buildGameDetailsPath, + profileBackgroundFromProfileImage, + steamUrlBuilder, +} from "@renderer/helpers"; import { PersonIcon, PlusIcon, TelescopeIcon } from "@primer/octicons-react"; import { Button, Link } from "@renderer/components"; import { UserEditProfileModal } from "./user-edit-modal"; @@ -41,6 +45,8 @@ export function UserContent({ } = useUserDetails(); const { showSuccessToast } = useToast(); + const [profileContentBoxBackground, setProfileContentBoxBackground] = + useState(); const [showEditProfileModal, setShowEditProfileModal] = useState(false); const [showSignOutModal, setShowSignOutModal] = useState(false); @@ -96,11 +102,19 @@ export function UserContent({ if (isMe) updateFriendRequests(); }, [isMe]); - const profileContentBoxBackground = useMemo(() => { - if (profileBackground) return profileBackground; - /* TODO: Render background colors for other users */ - return undefined; - }, [profileBackground]); + useEffect(() => { + if (isMe && profileBackground) { + setProfileContentBoxBackground(profileBackground); + } + + if (userProfile.profileImageUrl) { + profileBackgroundFromProfileImage(userProfile.profileImageUrl).then( + (profileBackground) => { + setProfileContentBoxBackground(profileBackground); + } + ); + } + }, [profileBackground, isMe]); return ( <> From d350aa950dc4bb52000be5980bd0e737127ee72c Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 21 Jul 2024 16:35:02 -0300 Subject: [PATCH 02/21] feat: get friends event --- src/main/events/user/get-user-friends.ts | 26 ++++++++++++++++++++ src/main/events/user/get-user.ts | 8 ++++-- src/main/services/hydra-api.ts | 6 ++--- src/renderer/src/pages/user/user-content.tsx | 6 ++--- src/types/index.ts | 7 +++++- 5 files changed, 44 insertions(+), 9 deletions(-) create mode 100644 src/main/events/user/get-user-friends.ts diff --git a/src/main/events/user/get-user-friends.ts b/src/main/events/user/get-user-friends.ts new file mode 100644 index 00000000..a82673eb --- /dev/null +++ b/src/main/events/user/get-user-friends.ts @@ -0,0 +1,26 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import { UserFriends } from "@types"; + +export const getUserFriends = async ( + userId: string, + take: number, + skip: number +): Promise => { + return HydraApi.get(`/user/${userId}/friends`, { take, skip }).catch( + (_err) => { + return { totalFriends: 0, friends: [] } as UserFriends; + } + ); +}; + +const getUserFriendsEvent = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string, + take: number, + skip: number +) => { + return getUserFriends(userId, take, skip); +}; + +registerEvent("getUserFriends", getUserFriendsEvent); diff --git a/src/main/events/user/get-user.ts b/src/main/events/user/get-user.ts index 7b4c0aa8..98b98a77 100644 --- a/src/main/events/user/get-user.ts +++ b/src/main/events/user/get-user.ts @@ -4,13 +4,17 @@ import { steamGamesWorker } from "@main/workers"; import { UserProfile } from "@types"; import { convertSteamGameToCatalogueEntry } from "../helpers/search-games"; import { getSteamAppAsset } from "@main/helpers"; +import { getUserFriends } from "./get-user-friends"; const getUser = async ( _event: Electron.IpcMainInvokeEvent, userId: string ): Promise => { try { - const profile = await HydraApi.get(`/user/${userId}`); + const [profile, friends] = await Promise.all([ + HydraApi.get(`/user/${userId}`), + getUserFriends(userId, 12, 0), + ]); const recentGames = await Promise.all( profile.recentGames.map(async (game) => { @@ -46,7 +50,7 @@ const getUser = async ( }) ); - return { ...profile, libraryGames, recentGames }; + return { ...profile, libraryGames, recentGames, friends }; } catch (err) { return null; } diff --git a/src/main/services/hydra-api.ts b/src/main/services/hydra-api.ts index 97517b5b..6547ec15 100644 --- a/src/main/services/hydra-api.ts +++ b/src/main/services/hydra-api.ts @@ -72,7 +72,7 @@ export class HydraApi { this.instance.interceptors.request.use( (request) => { logger.log(" ---- REQUEST -----"); - logger.log(request.method, request.url, request.data); + logger.log(request.method, request.url, request.params, request.data); return request; }, (error) => { @@ -196,12 +196,12 @@ export class HydraApi { throw err; }; - static async get(url: string) { + static async get(url: string, params?: any) { if (!this.isLoggedIn()) throw new UserNotLoggedInError(); await this.revalidateAccessTokenIfExpired(); return this.instance - .get(url, this.getAxiosConfig()) + .get(url, { params, ...this.getAxiosConfig() }) .then((response) => response.data) .catch(this.handleUnauthorizedError); } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index cf332405..c22aae4e 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -342,7 +342,7 @@ export function UserContent({ {(isMe || - (userProfile.friends && userProfile.friends.length > 0)) && ( + (userProfile.friends && userProfile.friends.totalFriends > 0)) && (
@@ -369,7 +369,7 @@ export function UserContent({ gap: `${SPACING_UNIT}px`, }} > - {userProfile.friends.map((friend) => { + {userProfile.friends.friends.map((friend) => { return (
@@ -72,6 +77,7 @@ export const UserFriendRequest = ({ @@ -80,12 +86,14 @@ export const UserFriendRequest = ({ diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index c22aae4e..6d9401fb 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -17,7 +17,13 @@ import { profileBackgroundFromProfileImage, steamUrlBuilder, } from "@renderer/helpers"; -import { PersonIcon, PlusIcon, TelescopeIcon } from "@primer/octicons-react"; +import { + CheckCircleIcon, + PersonIcon, + PlusIcon, + TelescopeIcon, + XCircleIcon, +} from "@primer/octicons-react"; import { Button, Link } from "@renderer/components"; import { UserEditProfileModal } from "./user-edit-modal"; import { UserSignOutModal } from "./user-signout-modal"; @@ -39,6 +45,7 @@ export function UserContent({ const { userDetails, profileBackground, + friendRequests, signOut, updateFriendRequests, showFriendsModal, @@ -116,6 +123,71 @@ export function UserContent({ } }, [profileBackground, isMe]); + const getProfileActions = () => { + if (isMe) { + return ( + <> + + + + + ); + } + + const friendRequest = friendRequests.find( + (request) => request.id == userProfile.id + ); + + if (!friendRequest) { + return ( + <> + + + + + ); + } + + if (friendRequest.type === "RECEIVED") { + return ( + <> + + + + ); + } + + return ( + + ); + }; + return ( <> - {isMe && ( +
-
- <> - - - - -
+ {getProfileActions()}
- )} +
diff --git a/src/renderer/src/pages/user/user.css.ts b/src/renderer/src/pages/user/user.css.ts index 3df92ceb..f9b1b09a 100644 --- a/src/renderer/src/pages/user/user.css.ts +++ b/src/renderer/src/pages/user/user.css.ts @@ -279,3 +279,16 @@ export const profileBackground = style({ top: "0", borderRadius: "4px", }); + +export const cancelRequestButton = style({ + cursor: "pointer", + color: vars.color.body, + ":hover": { + color: vars.color.danger, + }, +}); + +export const acceptRequestButton = style({ + cursor: "pointer", + color: vars.color.success, +}); diff --git a/src/types/index.ts b/src/types/index.ts index 826c768f..1d91af62 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -293,6 +293,7 @@ export interface UserProfile { id: string; displayName: string; profileImageUrl: string | null; + profileVisibility: "PUBLIC" | "PRIVATE" | "FRIEND"; totalPlayTimeInSeconds: number; libraryGames: UserGame[]; recentGames: UserGame[]; From e642bf71b1741ca21a88e9b925ae70b90f286e0d Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Sun, 21 Jul 2024 20:49:37 -0300 Subject: [PATCH 05/21] feat: rename functions --- src/renderer/src/app.tsx | 6 ++-- src/renderer/src/hooks/use-user-details.ts | 14 ++++----- .../user-friend-modal-add-friend.tsx | 12 +++---- src/renderer/src/pages/user/user-content.tsx | 31 +++++++++++++++---- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 24c7bed6..689a338e 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -42,7 +42,7 @@ export function App() { const { isFriendsModalVisible, friendRequetsModalTab, - updateFriendRequests, + fetchFriendRequests, hideFriendsModal, } = useUserDetails(); @@ -104,7 +104,7 @@ export function App() { fetchUserDetails().then((response) => { if (response) { updateUserDetails(response); - updateFriendRequests(); + fetchFriendRequests(); } }); }, [fetchUserDetails, updateUserDetails, dispatch]); @@ -113,7 +113,7 @@ export function App() { fetchUserDetails().then((response) => { if (response) { updateUserDetails(response); - updateFriendRequests(); + fetchFriendRequests(); showSuccessToast(t("successfully_signed_in")); } }); diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 80ef8b4c..8d53d996 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -84,7 +84,7 @@ export function useUserDetails() { [updateUserDetails] ); - const updateFriendRequests = useCallback(async () => { + const fetchFriendRequests = useCallback(async () => { const friendRequests = await window.electron.getFriendRequests(); dispatch(setFriendRequests(friendRequests)); }, [dispatch]); @@ -92,7 +92,7 @@ export function useUserDetails() { const showFriendsModal = useCallback( (tab: UserFriendModalTab) => { dispatch(setFriendsModalVisible(tab)); - updateFriendRequests(); + fetchFriendRequests(); }, [dispatch] ); @@ -105,18 +105,18 @@ export function useUserDetails() { async (userId: string) => { return window.electron .sendFriendRequest(userId) - .then(() => updateFriendRequests()); + .then(() => fetchFriendRequests()); }, - [updateFriendRequests] + [fetchFriendRequests] ); const updateFriendRequestState = useCallback( async (userId: string, action: FriendRequestAction) => { return window.electron .updateFriendRequest(userId, action) - .then(() => updateFriendRequests()); + .then(() => fetchFriendRequests()); }, - [updateFriendRequests] + [fetchFriendRequests] ); return { @@ -133,7 +133,7 @@ export function useUserDetails() { updateUserDetails, patchUser, sendFriendRequest, - updateFriendRequests, + fetchFriendRequests, updateFriendRequestState, }; } diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx index bf4879b2..62290095 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx @@ -56,19 +56,19 @@ export const UserFriendModalAddFriend = ({ navigate(`/user/${friendCode}`); }; - const handleClickCancelFriendRequest = (userId: string) => { + const handleCancelFriendRequest = (userId: string) => { updateFriendRequestState(userId, "CANCEL").catch(() => { showErrorToast("Falha ao cancelar convite"); }); }; - const handleClickAcceptFriendRequest = (userId: string) => { + const handleAcceptFriendRequest = (userId: string) => { updateFriendRequestState(userId, "ACCEPTED").catch(() => { showErrorToast("Falha ao aceitar convite"); }); }; - const handleClickRefuseFriendRequest = (userId: string) => { + const handleRefuseFriendRequest = (userId: string) => { updateFriendRequestState(userId, "REFUSED").catch(() => { showErrorToast("Falha ao recusar convite"); }); @@ -127,9 +127,9 @@ export const UserFriendModalAddFriend = ({ isRequestSent={request.type === "SENT"} profileImageUrl={request.profileImageUrl} userId={request.id} - onClickAcceptRequest={handleClickAcceptFriendRequest} - onClickCancelRequest={handleClickCancelFriendRequest} - onClickRefuseRequest={handleClickRefuseFriendRequest} + onClickAcceptRequest={handleAcceptFriendRequest} + onClickCancelRequest={handleCancelFriendRequest} + onClickRefuseRequest={handleRefuseFriendRequest} onClickRequest={handleClickRequest} /> ); diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 6d9401fb..3ace2bd1 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -47,10 +47,11 @@ export function UserContent({ profileBackground, friendRequests, signOut, - updateFriendRequests, + fetchFriendRequests, showFriendsModal, + updateFriendRequestState, } = useUserDetails(); - const { showSuccessToast } = useToast(); + const { showSuccessToast, showErrorToast } = useToast(); const [profileContentBoxBackground, setProfileContentBoxBackground] = useState(); @@ -106,7 +107,7 @@ export function UserContent({ const isMe = userDetails?.id == userProfile.id; useEffect(() => { - if (isMe) updateFriendRequests(); + if (isMe) fetchFriendRequests(); }, [isMe]); useEffect(() => { @@ -123,6 +124,24 @@ export function UserContent({ } }, [profileBackground, isMe]); + const handleCancelFriendRequest = (userId: string) => { + updateFriendRequestState(userId, "CANCEL").catch(() => { + showErrorToast("Falha ao cancelar convite"); + }); + }; + + const handleAcceptFriendRequest = (userId: string) => { + updateFriendRequestState(userId, "ACCEPTED").catch(() => { + showErrorToast("Falha ao aceitar convite"); + }); + }; + + const handleRefuseFriendRequest = (userId: string) => { + updateFriendRequestState(userId, "REFUSED").catch(() => { + showErrorToast("Falha ao recusar convite"); + }); + }; + const getProfileActions = () => { if (isMe) { return ( @@ -162,14 +181,14 @@ export function UserContent({ @@ -181,7 +200,7 @@ export function UserContent({ From 11c29355e34339db63ba05810b35e9099c2ea5fa Mon Sep 17 00:00:00 2001 From: Lianela <140931995+Lianela@users.noreply.github.com> Date: Mon, 22 Jul 2024 19:38:34 -0600 Subject: [PATCH 06/21] Friend strings translated (ES) --- src/locales/es/translation.json | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/locales/es/translation.json b/src/locales/es/translation.json index 5e016d34..fcb2b099 100644 --- a/src/locales/es/translation.json +++ b/src/locales/es/translation.json @@ -241,6 +241,15 @@ "successfully_signed_out": "Sesión cerrada exitosamente", "sign_out": "Cerrar sesión", "playing_for": "Jugando por {{amount}}", - "sign_out_modal_text": "Tu biblioteca se ha vinculado con tu cuenta. Cuando cierres sesión, tú biblioteca ya no será visible y cualquier progreso no se guardará. ¿Continuar con el cierre de sesión?" + "sign_out_modal_text": "Tu biblioteca se ha vinculado con tu cuenta. Cuando cierres sesión, tú biblioteca ya no será visible y cualquier progreso no se guardará. ¿Continuar con el cierre de sesión?", + "add_friends": "Añadir amigos", + "add": "Añadir", + "friend_code": "Código de amigo", + "see_profile": "Ver perfil", + "sending": "Enviando", + "friend_request_sent": "Solicitud de amistad enviada", + "friends": "Amigos", + "friends_list": "Lista de amigos", + "user_not_found": "Usuario no encontrado" } } From 380143c780c585f2f9e83f0eb349b636976d99d8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 23 Jul 2024 15:30:04 -0300 Subject: [PATCH 07/21] feat: correctly get own friends --- src/main/events/user/get-user-friends.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/events/user/get-user-friends.ts b/src/main/events/user/get-user-friends.ts index a82673eb..4505909a 100644 --- a/src/main/events/user/get-user-friends.ts +++ b/src/main/events/user/get-user-friends.ts @@ -1,3 +1,4 @@ +import { userAuthRepository } from "@main/repository"; import { registerEvent } from "../register-event"; import { HydraApi } from "@main/services"; import { UserFriends } from "@types"; @@ -7,9 +8,19 @@ export const getUserFriends = async ( take: number, skip: number ): Promise => { + const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); + + if (loggedUser?.userId == userId) { + return HydraApi.get(`/profile/friends`, { take, skip }).catch( + (_err) => { + return { totalFriends: 0, friends: [] }; + } + ); + } + return HydraApi.get(`/user/${userId}/friends`, { take, skip }).catch( (_err) => { - return { totalFriends: 0, friends: [] } as UserFriends; + return { totalFriends: 0, friends: [] }; } ); }; From 010f07373d797534a3e6287aff0fcfc2603fa52a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 23 Jul 2024 18:37:19 -0300 Subject: [PATCH 08/21] feat: add getUserFriends event --- src/main/events/index.ts | 1 + src/main/events/user/get-user-friends.ts | 20 +++++++++++--------- src/preload/index.ts | 1 + src/renderer/src/declaration.d.ts | 2 ++ 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/main/events/index.ts b/src/main/events/index.ts index dd5e3263..398235f0 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -43,6 +43,7 @@ import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; import "./user/get-user"; +import "./user/get-user-friends"; import "./profile/get-friend-requests"; import "./profile/get-me"; import "./profile/update-friend-request"; diff --git a/src/main/events/user/get-user-friends.ts b/src/main/events/user/get-user-friends.ts index 4505909a..a9394d95 100644 --- a/src/main/events/user/get-user-friends.ts +++ b/src/main/events/user/get-user-friends.ts @@ -8,21 +8,23 @@ export const getUserFriends = async ( take: number, skip: number ): Promise => { - const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); + try { + const loggedUser = await userAuthRepository.findOne({ where: { id: 1 } }); - if (loggedUser?.userId == userId) { - return HydraApi.get(`/profile/friends`, { take, skip }).catch( + if (loggedUser?.userId == userId) { + return HydraApi.get(`/profile/friends`, { take, skip }).catch((_err) => { + return { totalFriends: 0, friends: [] }; + }); + } + + return HydraApi.get(`/user/${userId}/friends`, { take, skip }).catch( (_err) => { return { totalFriends: 0, friends: [] }; } ); + } catch (err) { + return { totalFriends: 0, friends: [] }; } - - return HydraApi.get(`/user/${userId}/friends`, { take, skip }).catch( - (_err) => { - return { totalFriends: 0, friends: [] }; - } - ); }; const getUserFriendsEvent = async ( diff --git a/src/preload/index.ts b/src/preload/index.ts index 91722606..e6dafe0f 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -145,6 +145,7 @@ contextBridge.exposeInMainWorld("electron", { /* User */ getUser: (userId: string) => ipcRenderer.invoke("getUser", userId), + getUserFriends: (userId: string) => ipcRenderer.invoke("getUserFriends", userId), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index bb89f84e..55fc1269 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -16,6 +16,7 @@ import type { UserProfile, FriendRequest, FriendRequestAction, + UserFriends, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -127,6 +128,7 @@ declare global { /* User */ getUser: (userId: string) => Promise; + getUserFriends: (userId: string) => Promise; /* Profile */ getMe: () => Promise; From a196b91cb9a744b6bac644611de51ea60a7328b4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:27:38 -0300 Subject: [PATCH 09/21] feat: pass userId to modal --- src/preload/index.ts | 3 +- src/renderer/src/app.tsx | 16 +++++--- .../components/sidebar/sidebar-profile.tsx | 4 +- src/renderer/src/declaration.d.ts | 6 ++- .../src/features/user-details-slice.ts | 7 +++- src/renderer/src/hooks/use-user-details.ts | 6 ++- .../user-friend-modal-list.tsx | 40 ++++++++++++++++++ .../user-friend-modal/user-friend-modal.tsx | 41 +++++++++++-------- src/renderer/src/pages/user/user-content.tsx | 12 +++++- 9 files changed, 104 insertions(+), 31 deletions(-) create mode 100644 src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx diff --git a/src/preload/index.ts b/src/preload/index.ts index e6dafe0f..cd3b9686 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -145,7 +145,8 @@ contextBridge.exposeInMainWorld("electron", { /* User */ getUser: (userId: string) => ipcRenderer.invoke("getUser", userId), - getUserFriends: (userId: string) => ipcRenderer.invoke("getUserFriends", userId), + getUserFriends: (userId: string, take: number, skip: number) => + ipcRenderer.invoke("getUserFriends", userId, take, skip), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 689a338e..8c6f7604 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -42,11 +42,12 @@ export function App() { const { isFriendsModalVisible, friendRequetsModalTab, + friendModalUserId, fetchFriendRequests, hideFriendsModal, } = useUserDetails(); - const { fetchUserDetails, updateUserDetails, clearUserDetails } = + const { userDetails, fetchUserDetails, updateUserDetails, clearUserDetails } = useUserDetails(); const dispatch = useAppDispatch(); @@ -218,11 +219,14 @@ export function App() { onClose={handleToastClose} /> - + {userDetails && ( + + )}
diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index 66c4d82d..3ff598f7 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -84,7 +84,9 @@ export function SidebarProfile() { - ); - })} - + {isMe && ( +
+ {tabs.map((tab, index) => { + return ( + + ); + })} +
+ )}

{tabs[currentTab]}

{renderTab()}
diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 3ace2bd1..9fb1c8b5 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -424,7 +424,12 @@ export function UserContent({
+ ); + } + + if (type === "RECEIVED") { + return ( + <> + + + + ); + } + + return ( + + ); + }; + return (
@@ -73,32 +126,7 @@ export const UserFriendRequest = ({ gap: `${SPACING_UNIT}px`, }} > - {isRequestSent ? ( - - ) : ( - <> - - - - )} + {getRequestActions()}
); diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx index 62290095..f71d4790 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx @@ -4,7 +4,7 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; -import { UserFriendRequest } from "./user-friend-request"; +import { UserFriendItem } from "./user-friend-item"; export interface UserFriendModalAddFriendProps { closeModal: () => void; @@ -121,16 +121,16 @@ export const UserFriendModalAddFriend = ({

Pendentes

{friendRequests.map((request) => { return ( - ); })} diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx index 83d7a6a0..2c639847 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx @@ -1,13 +1,22 @@ +import { SPACING_UNIT } from "@renderer/theme.css"; import { UserFriend } from "@types"; import { useEffect, useState } from "react"; +import { UserFriendItem } from "./user-friend-item"; +import { useNavigate } from "react-router-dom"; export interface UserFriendModalListProps { userId: string; + closeModal: () => void; } const pageSize = 12; -export const UserFriendModalList = ({ userId }: UserFriendModalListProps) => { +export const UserFriendModalList = ({ + userId, + closeModal, +}: UserFriendModalListProps) => { + const navigate = useNavigate(); + const [page, setPage] = useState(0); const [maxPage, setMaxPage] = useState(0); const [friends, setFriends] = useState([]); @@ -34,7 +43,34 @@ export const UserFriendModalList = ({ userId }: UserFriendModalListProps) => { loadNextPage(); }, [userId]); - return friends.map((friend) => { - return

{friend.displayName}

; - }); + const handleClickFriend = (userId: string) => { + closeModal(); + navigate(`/user/${userId}`); + }; + + return ( +
+ {friends.map((friend) => { + return ( + {}} + onClickCancelRequest={() => {}} + onClickRefuseRequest={() => {}} + onClickItem={handleClickFriend} + type={"ACCEPTED"} + key={friend.id} + /> + ); + })} +
+ ); }; diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx index b0752de7..39a82ec4 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx @@ -43,7 +43,7 @@ export const UserFriendModal = ({ const renderTab = () => { if (currentTab == UserFriendModalTab.FriendsList) { - return ; + return ; } if (currentTab == UserFriendModalTab.AddFriend) { From 94bd691209c7cd42bbc3d6a5c6533c18d93f321a Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:12:14 -0300 Subject: [PATCH 11/21] fet: show only received friends request on sidebar icon --- .../src/components/sidebar/sidebar-profile.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index 3ff598f7..b16c2c28 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -3,10 +3,11 @@ import { PersonAddIcon, PersonIcon } from "@primer/octicons-react"; import * as styles from "./sidebar-profile.css"; import { assignInlineVars } from "@vanilla-extract/dynamic"; import { useAppSelector, useUserDetails } from "@renderer/hooks"; -import { useMemo } from "react"; +import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { profileContainerBackground } from "./sidebar-profile.css"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; +import { FriendRequest } from "@types"; export function SidebarProfile() { const navigate = useNavigate(); @@ -16,6 +17,14 @@ export function SidebarProfile() { const { userDetails, profileBackground, friendRequests, showFriendsModal } = useUserDetails(); + const [receivedRequests, setReceivedRequests] = useState([]); + + useEffect(() => { + setReceivedRequests( + receivedRequests.filter((request) => request.type === "RECEIVED") + ); + }, [friendRequests]); + const { gameRunning } = useAppSelector((state) => state.gameRunning); const handleButtonClick = () => { @@ -79,7 +88,7 @@ export function SidebarProfile() { )} - {userDetails && friendRequests.length > 0 && !gameRunning && ( + {userDetails && receivedRequests.length > 0 && !gameRunning && (
)} From 102299e42fad776f55d639cde932be5bd4843b04 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:12:34 -0300 Subject: [PATCH 12/21] feat: remove tab title --- .../pages/shared-modals/user-friend-modal/user-friend-modal.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx index 39a82ec4..abc26270 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal.tsx @@ -78,7 +78,6 @@ export const UserFriendModal = ({ })} )} -

{tabs[currentTab]}

{renderTab()} From 304aa011ad6b6cb0bff01de3cb83535eb7185602 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 25 Jul 2024 19:27:03 -0300 Subject: [PATCH 13/21] feat: action buttons on user profile page --- src/locales/en/translation.json | 3 +- src/locales/pt/translation.json | 3 +- src/renderer/index.html | 2 +- .../components/sidebar/sidebar-profile.tsx | 2 +- src/renderer/src/hooks/use-user-details.ts | 10 +- .../user-friend-modal-add-friend.tsx | 16 ++- src/renderer/src/pages/user/user-content.tsx | 103 ++++++++++++------ src/types/index.ts | 9 ++ 8 files changed, 101 insertions(+), 47 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 24ba8f0f..dc1bb1d7 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -258,6 +258,7 @@ "accept_request": "Accept request", "ignore_request": "Ignore request", "cancel_request": "Cancel request", - "undo_friendship": "Undo friendship" + "undo_friendship": "Undo friendship", + "request_accepted": "Request accepted" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index ce4efd58..c95ae82f 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -258,6 +258,7 @@ "accept_request": "Aceitar pedido", "ignore_request": "Ignorar pedido", "cancel_request": "Cancelar pedido", - "undo_friendship": "Desfazer amizade" + "undo_friendship": "Desfazer amizade", + "request_accepted": "Pedido de amizade aceito" } } diff --git a/src/renderer/index.html b/src/renderer/index.html index 52276268..543b85a9 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -6,7 +6,7 @@ Hydra diff --git a/src/renderer/src/components/sidebar/sidebar-profile.tsx b/src/renderer/src/components/sidebar/sidebar-profile.tsx index b16c2c28..81736e37 100644 --- a/src/renderer/src/components/sidebar/sidebar-profile.tsx +++ b/src/renderer/src/components/sidebar/sidebar-profile.tsx @@ -21,7 +21,7 @@ export function SidebarProfile() { useEffect(() => { setReceivedRequests( - receivedRequests.filter((request) => request.type === "RECEIVED") + friendRequests.filter((request) => request.type === "RECEIVED") ); }, [friendRequests]); diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 401f4684..38f6a8f3 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -85,9 +85,13 @@ export function useUserDetails() { [updateUserDetails] ); - const fetchFriendRequests = useCallback(async () => { - const friendRequests = await window.electron.getFriendRequests(); - dispatch(setFriendRequests(friendRequests)); + const fetchFriendRequests = useCallback(() => { + return window.electron + .getFriendRequests() + .then((friendRequests) => { + dispatch(setFriendRequests(friendRequests)); + }) + .catch(() => {}); }, [dispatch]); const showFriendsModal = useCallback( diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx index f71d4790..0725674e 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-add-friend.tsx @@ -23,7 +23,7 @@ export const UserFriendModalAddFriend = ({ const { sendFriendRequest, updateFriendRequestState, friendRequests } = useUserDetails(); - const { showErrorToast } = useToast(); + const { showSuccessToast, showErrorToast } = useToast(); const handleClickAddFriend = () => { setIsAddingFriend(true); @@ -58,19 +58,23 @@ export const UserFriendModalAddFriend = ({ const handleCancelFriendRequest = (userId: string) => { updateFriendRequestState(userId, "CANCEL").catch(() => { - showErrorToast("Falha ao cancelar convite"); + showErrorToast(t("try_again")); }); }; const handleAcceptFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "ACCEPTED").catch(() => { - showErrorToast("Falha ao aceitar convite"); - }); + updateFriendRequestState(userId, "ACCEPTED") + .then(() => { + showSuccessToast(t("request_accepted")); + }) + .catch(() => { + showErrorToast(t("try_again")); + }); }; const handleRefuseFriendRequest = (userId: string) => { updateFriendRequestState(userId, "REFUSED").catch(() => { - showErrorToast("Falha ao recusar convite"); + showErrorToast(t("try_again")); }); }; diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 9fb1c8b5..228fdbd9 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -1,4 +1,4 @@ -import { UserGame, UserProfile } from "@types"; +import { UserGame, UserProfile, UserRelation } from "@types"; import cn from "classnames"; import * as styles from "./user.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; @@ -45,8 +45,8 @@ export function UserContent({ const { userDetails, profileBackground, - friendRequests, signOut, + sendFriendRequest, fetchFriendRequests, showFriendsModal, updateFriendRequestState, @@ -124,22 +124,47 @@ export function UserContent({ } }, [profileBackground, isMe]); + const handleUndoFriendship = (userRelation: UserRelation) => { + const userId = + userRelation.AId === userProfile.id ? userRelation.BId : userRelation.AId; + + updateFriendRequestState(userId, "CANCEL") + .then(updateUserProfile) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + + const handleSendFriendRequest = () => { + sendFriendRequest(userProfile.id) + .then(updateUserProfile) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + const handleCancelFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "CANCEL").catch(() => { - showErrorToast("Falha ao cancelar convite"); - }); + updateFriendRequestState(userId, "CANCEL") + .then(updateUserProfile) + .catch(() => { + showErrorToast(t("try_again")); + }); }; const handleAcceptFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "ACCEPTED").catch(() => { - showErrorToast("Falha ao aceitar convite"); - }); + updateFriendRequestState(userId, "ACCEPTED") + .then(updateUserProfile) + .catch(() => { + showErrorToast(t("try_again")); + }); }; const handleRefuseFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "REFUSED").catch(() => { - showErrorToast("Falha ao recusar convite"); - }); + updateFriendRequestState(userId, "REFUSED") + .then(updateUserProfile) + .catch(() => { + showErrorToast(t("try_again")); + }); }; const getProfileActions = () => { @@ -157,14 +182,10 @@ export function UserContent({ ); } - const friendRequest = friendRequests.find( - (request) => request.id == userProfile.id - ); - - if (!friendRequest) { + if (userProfile.relation == null) { return ( <> - @@ -175,35 +196,49 @@ export function UserContent({ ); } - if (friendRequest.type === "RECEIVED") { + if (userProfile.relation.status === "ACCEPTED") { return ( <> - ); } + if (userProfile.relation.BId === userProfile.id) { + return ( + + ); + } + return ( - + <> + + + ); }; diff --git a/src/types/index.ts b/src/types/index.ts index 1d91af62..033739c4 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -289,6 +289,14 @@ export interface FriendRequest { type: "SENT" | "RECEIVED"; } +export interface UserRelation { + AId: string; + BId: string; + status: "ACCEPTED" | "PENDING"; + createdAt: string; + updatedAt: string; +} + export interface UserProfile { id: string; displayName: string; @@ -298,6 +306,7 @@ export interface UserProfile { libraryGames: UserGame[]; recentGames: UserGame[]; friends: UserFriends; + relation: UserRelation | null; } export interface DownloadSource { From edf920fed3228c2a3806e6bf8b21cba2ba81be10 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 25 Jul 2024 20:08:53 -0300 Subject: [PATCH 14/21] feat: block and unblock events --- src/locales/en/translation.json | 3 ++- src/locales/pt/translation.json | 3 ++- src/main/events/index.ts | 2 ++ src/main/events/user/block-user.ts | 11 +++++++++++ src/main/events/user/unblock-user.ts | 11 +++++++++++ src/preload/index.ts | 2 ++ src/renderer/src/declaration.d.ts | 2 ++ src/renderer/src/hooks/use-user-details.ts | 10 ++++++++++ src/renderer/src/pages/user/user-content.tsx | 14 +++++++++++++- 9 files changed, 55 insertions(+), 3 deletions(-) create mode 100644 src/main/events/user/block-user.ts create mode 100644 src/main/events/user/unblock-user.ts diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index dc1bb1d7..3385feb7 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -259,6 +259,7 @@ "ignore_request": "Ignore request", "cancel_request": "Cancel request", "undo_friendship": "Undo friendship", - "request_accepted": "Request accepted" + "request_accepted": "Request accepted", + "user_blocked_successfully": "User blocked successfully" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index c95ae82f..5d58b62f 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -259,6 +259,7 @@ "ignore_request": "Ignorar pedido", "cancel_request": "Cancelar pedido", "undo_friendship": "Desfazer amizade", - "request_accepted": "Pedido de amizade aceito" + "request_accepted": "Pedido de amizade aceito", + "user_blocked_successfully": "Usuário bloqueado com sucesso" } } diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 398235f0..5ae0db46 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -43,6 +43,8 @@ import "./auth/sign-out"; import "./auth/open-auth-window"; import "./auth/get-session-hash"; import "./user/get-user"; +import "./user/block-user"; +import "./user/unblock-user"; import "./user/get-user-friends"; import "./profile/get-friend-requests"; import "./profile/get-me"; diff --git a/src/main/events/user/block-user.ts b/src/main/events/user/block-user.ts new file mode 100644 index 00000000..303a5315 --- /dev/null +++ b/src/main/events/user/block-user.ts @@ -0,0 +1,11 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +const blockUser = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string +): Promise => { + return HydraApi.get(`/user/${userId}/block`); +}; + +registerEvent("block", blockUser); diff --git a/src/main/events/user/unblock-user.ts b/src/main/events/user/unblock-user.ts new file mode 100644 index 00000000..c1c8112e --- /dev/null +++ b/src/main/events/user/unblock-user.ts @@ -0,0 +1,11 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +const unblockUser = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string +): Promise => { + return HydraApi.get(`/user/${userId}/unblock`); +}; + +registerEvent("unblockUser", unblockUser); diff --git a/src/preload/index.ts b/src/preload/index.ts index cd3b9686..b7c368fa 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -145,6 +145,8 @@ contextBridge.exposeInMainWorld("electron", { /* User */ getUser: (userId: string) => ipcRenderer.invoke("getUser", userId), + blockUser: (userId: string) => ipcRenderer.invoke("blockUser", userId), + unblockUser: (userId: string) => ipcRenderer.invoke("unblockUser", userId), getUserFriends: (userId: string, take: number, skip: number) => ipcRenderer.invoke("getUserFriends", userId, take, skip), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index ad0b8953..564d7501 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -128,6 +128,8 @@ declare global { /* User */ getUser: (userId: string) => Promise; + blockUser: (userId: string) => Promise; + unblockUser: (userId: string) => Promise; getUserFriends: ( userId: string, take: number, diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 38f6a8f3..4b4d5b45 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -124,6 +124,14 @@ export function useUserDetails() { [fetchFriendRequests] ); + const blockUser = (userId: string) => { + return window.electron.blockUser(userId); + }; + + const unblockUser = (userId: string) => { + return window.electron.unblockUser(userId); + }; + return { userDetails, profileBackground, @@ -141,5 +149,7 @@ export function useUserDetails() { sendFriendRequest, fetchFriendRequests, updateFriendRequestState, + blockUser, + unblockUser, }; } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 228fdbd9..8224122b 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -50,6 +50,7 @@ export function UserContent({ fetchFriendRequests, showFriendsModal, updateFriendRequestState, + blockUser, } = useUserDetails(); const { showSuccessToast, showErrorToast } = useToast(); @@ -143,6 +144,17 @@ export function UserContent({ }); }; + const handleBlockUser = () => { + blockUser(userProfile.id) + .then(() => { + showSuccessToast(t("user_blocked_successfully")); + navigate(-1); + }) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + const handleCancelFriendRequest = (userId: string) => { updateFriendRequestState(userId, "CANCEL") .then(updateUserProfile) @@ -189,7 +201,7 @@ export function UserContent({ {t("add_friend")} - From 00c46bc98155989859d6f0d7f3e7c3837ec88ce8 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Thu, 25 Jul 2024 23:03:11 -0300 Subject: [PATCH 15/21] feat: implement undo friendship --- src/main/events/index.ts | 1 + src/main/events/profile/undo-friendship.ts | 11 +++++++++++ src/preload/index.ts | 2 ++ src/renderer/src/declaration.d.ts | 1 + src/renderer/src/hooks/use-user-details.ts | 5 +++++ src/renderer/src/pages/user/user-content.tsx | 7 +++++-- 6 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/main/events/profile/undo-friendship.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 5ae0db46..57daf51c 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -48,6 +48,7 @@ import "./user/unblock-user"; import "./user/get-user-friends"; import "./profile/get-friend-requests"; import "./profile/get-me"; +import "./profile/undo-friendship"; import "./profile/update-friend-request"; import "./profile/update-profile"; import "./profile/send-friend-request"; diff --git a/src/main/events/profile/undo-friendship.ts b/src/main/events/profile/undo-friendship.ts new file mode 100644 index 00000000..d6f858f1 --- /dev/null +++ b/src/main/events/profile/undo-friendship.ts @@ -0,0 +1,11 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +const undoFriendship = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string +): Promise => { + return HydraApi.delete(`/profile/friends/${userId}`); +}; + +registerEvent("undoFriendship", undoFriendship); diff --git a/src/preload/index.ts b/src/preload/index.ts index b7c368fa..3350a340 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -135,6 +135,8 @@ contextBridge.exposeInMainWorld("electron", { /* Profile */ getMe: () => ipcRenderer.invoke("getMe"), + undoFriendship: (userId: string) => + ipcRenderer.invoke("undoFriendship", userId), updateProfile: (displayName: string, newProfileImagePath: string | null) => ipcRenderer.invoke("updateProfile", displayName, newProfileImagePath), getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 564d7501..e022cffe 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -138,6 +138,7 @@ declare global { /* Profile */ getMe: () => Promise; + undoFriendship: (userId: string) => Promise; updateProfile: ( displayName: string, newProfileImagePath: string | null diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 4b4d5b45..21690e7e 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -124,6 +124,10 @@ export function useUserDetails() { [fetchFriendRequests] ); + const undoFriendship = (userId: string) => { + return window.electron.undoFriendship(userId); + }; + const blockUser = (userId: string) => { return window.electron.blockUser(userId); }; @@ -151,5 +155,6 @@ export function useUserDetails() { updateFriendRequestState, blockUser, unblockUser, + undoFriendship, }; } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 8224122b..ad591790 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -50,6 +50,7 @@ export function UserContent({ fetchFriendRequests, showFriendsModal, updateFriendRequestState, + undoFriendship, blockUser, } = useUserDetails(); const { showSuccessToast, showErrorToast } = useToast(); @@ -127,9 +128,11 @@ export function UserContent({ const handleUndoFriendship = (userRelation: UserRelation) => { const userId = - userRelation.AId === userProfile.id ? userRelation.BId : userRelation.AId; + userRelation.AId === userDetails?.id + ? userRelation.BId + : userRelation.AId; - updateFriendRequestState(userId, "CANCEL") + undoFriendship(userId) .then(updateUserProfile) .catch(() => { showErrorToast(t("try_again")); From 15269f3908cec1491641fb119f12418bf72760d3 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 26 Jul 2024 11:30:01 -0300 Subject: [PATCH 16/21] feat: show confirm modal for block action --- src/locales/en/translation.json | 3 +- src/locales/pt/translation.json | 3 +- src/main/events/user/block-user.ts | 4 +- src/main/events/user/unblock-user.ts | 2 +- .../src/pages/user/user-block-modal.tsx | 44 +++++++++++++++++++ src/renderer/src/pages/user/user-content.tsx | 12 ++++- .../src/pages/user/user-signout-modal.tsx | 4 +- 7 files changed, 64 insertions(+), 8 deletions(-) create mode 100644 src/renderer/src/pages/user/user-block-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 3385feb7..b24509d3 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -260,6 +260,7 @@ "cancel_request": "Cancel request", "undo_friendship": "Undo friendship", "request_accepted": "Request accepted", - "user_blocked_successfully": "User blocked successfully" + "user_blocked_successfully": "User blocked successfully", + "user_block_modal_text": "This will block {{displayName}}" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 5d58b62f..ef94c31f 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -260,6 +260,7 @@ "cancel_request": "Cancelar pedido", "undo_friendship": "Desfazer amizade", "request_accepted": "Pedido de amizade aceito", - "user_blocked_successfully": "Usuário bloqueado com sucesso" + "user_blocked_successfully": "Usuário bloqueado com sucesso", + "user_block_modal_text": "Bloquear {{displayName}}" } } diff --git a/src/main/events/user/block-user.ts b/src/main/events/user/block-user.ts index 303a5315..041b6530 100644 --- a/src/main/events/user/block-user.ts +++ b/src/main/events/user/block-user.ts @@ -5,7 +5,7 @@ const blockUser = async ( _event: Electron.IpcMainInvokeEvent, userId: string ): Promise => { - return HydraApi.get(`/user/${userId}/block`); + return HydraApi.post(`/user/${userId}/block`); }; -registerEvent("block", blockUser); +registerEvent("blockUser", blockUser); diff --git a/src/main/events/user/unblock-user.ts b/src/main/events/user/unblock-user.ts index c1c8112e..57cdb236 100644 --- a/src/main/events/user/unblock-user.ts +++ b/src/main/events/user/unblock-user.ts @@ -5,7 +5,7 @@ const unblockUser = async ( _event: Electron.IpcMainInvokeEvent, userId: string ): Promise => { - return HydraApi.get(`/user/${userId}/unblock`); + return HydraApi.post(`/user/${userId}/unblock`); }; registerEvent("unblockUser", unblockUser); diff --git a/src/renderer/src/pages/user/user-block-modal.tsx b/src/renderer/src/pages/user/user-block-modal.tsx new file mode 100644 index 00000000..e179e4da --- /dev/null +++ b/src/renderer/src/pages/user/user-block-modal.tsx @@ -0,0 +1,44 @@ +import { Button, Modal } from "@renderer/components"; +import * as styles from "./user.css"; +import { useTranslation } from "react-i18next"; + +export interface UserBlockModalProps { + visible: boolean; + displayName: string; + onConfirm: () => void; + onClose: () => void; +} + +export const UserBlockModal = ({ + visible, + displayName, + onConfirm, + onClose, +}: UserBlockModalProps) => { + const { t } = useTranslation("user_profile"); + + return ( + <> + +
+

+ {t("user_block_modal_text", { displayName })} +

+
+ + + +
+
+
+ + ); +}; diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index ad591790..88d9892d 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -28,6 +28,7 @@ import { Button, Link } from "@renderer/components"; import { UserEditProfileModal } from "./user-edit-modal"; import { UserSignOutModal } from "./user-signout-modal"; import { UserFriendModalTab } from "../shared-modals/user-friend-modal"; +import { UserBlockModal } from "./user-block-modal"; const MAX_MINUTES_TO_SHOW_IN_PLAYTIME = 120; @@ -59,6 +60,7 @@ export function UserContent({ useState(); const [showEditProfileModal, setShowEditProfileModal] = useState(false); const [showSignOutModal, setShowSignOutModal] = useState(false); + const [showUserBlockModal, setShowUserBlockModal] = useState(false); const { gameRunning } = useAppSelector((state) => state.gameRunning); @@ -150,6 +152,7 @@ export function UserContent({ const handleBlockUser = () => { blockUser(userProfile.id) .then(() => { + setShowUserBlockModal(false); showSuccessToast(t("user_blocked_successfully")); navigate(-1); }) @@ -204,7 +207,7 @@ export function UserContent({ {t("add_friend")} - @@ -272,6 +275,13 @@ export function UserContent({ onConfirm={handleConfirmSignout} /> + setShowUserBlockModal(false)} + onConfirm={handleBlockUser} + displayName={userProfile.displayName} + /> +
void; onClose: () => void; @@ -12,7 +12,7 @@ export const UserSignOutModal = ({ visible, onConfirm, onClose, -}: UserEditProfileModalProps) => { +}: UserSignOutModalProps) => { const { t } = useTranslation("user_profile"); return ( From d60242a62cb3a72bf7b847d7e4b045e6b9356af1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Mon, 29 Jul 2024 19:10:53 -0300 Subject: [PATCH 17/21] feat: code review --- src/main/events/user/get-user.ts | 8 +- src/renderer/src/pages/user/user-content.tsx | 118 ++++++++---------- ...nout-modal.tsx => user-sign-out-modal.tsx} | 0 src/types/index.ts | 3 +- 4 files changed, 62 insertions(+), 67 deletions(-) rename src/renderer/src/pages/user/{user-signout-modal.tsx => user-sign-out-modal.tsx} (100%) diff --git a/src/main/events/user/get-user.ts b/src/main/events/user/get-user.ts index 98b98a77..96a93042 100644 --- a/src/main/events/user/get-user.ts +++ b/src/main/events/user/get-user.ts @@ -50,7 +50,13 @@ const getUser = async ( }) ); - return { ...profile, libraryGames, recentGames, friends }; + return { + ...profile, + libraryGames, + recentGames, + friends: friends.friends, + totalFriends: friends.totalFriends, + }; } catch (err) { return null; } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 88d9892d..81db119d 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -1,4 +1,4 @@ -import { UserGame, UserProfile, UserRelation } from "@types"; +import { FriendRequestAction, UserGame, UserProfile } from "@types"; import cn from "classnames"; import * as styles from "./user.css"; import { SPACING_UNIT, vars } from "@renderer/theme.css"; @@ -26,7 +26,7 @@ import { } from "@primer/octicons-react"; import { Button, Link } from "@renderer/components"; import { UserEditProfileModal } from "./user-edit-modal"; -import { UserSignOutModal } from "./user-signout-modal"; +import { UserSignOutModal } from "./user-sign-out-modal"; import { UserFriendModalTab } from "../shared-modals/user-friend-modal"; import { UserBlockModal } from "./user-block-modal"; @@ -37,6 +37,8 @@ export interface ProfileContentProps { updateUserProfile: () => Promise; } +type FriendAction = FriendRequestAction | ("BLOCK" | "UNDO" | "SEND"); + export function UserContent({ userProfile, updateUserProfile, @@ -128,62 +130,35 @@ export function UserContent({ } }, [profileBackground, isMe]); - const handleUndoFriendship = (userRelation: UserRelation) => { - const userId = - userRelation.AId === userDetails?.id - ? userRelation.BId - : userRelation.AId; + const handleFriendAction = (userId: string, action: FriendAction) => { + try { + if (action === "UNDO") { + undoFriendship(userId).then(updateUserProfile); + return; + } - undoFriendship(userId) - .then(updateUserProfile) - .catch(() => { - showErrorToast(t("try_again")); - }); + if (action === "BLOCK") { + blockUser(userId).then(() => { + setShowUserBlockModal(false); + showSuccessToast(t("user_blocked_successfully")); + navigate(-1); + }); + + return; + } + + if (action === "SEND") { + sendFriendRequest(userProfile.id).then(updateUserProfile); + return; + } + + updateFriendRequestState(userId, action).then(updateUserProfile); + } catch (err) { + showErrorToast(t("try_again")); + } }; - const handleSendFriendRequest = () => { - sendFriendRequest(userProfile.id) - .then(updateUserProfile) - .catch(() => { - showErrorToast(t("try_again")); - }); - }; - - const handleBlockUser = () => { - blockUser(userProfile.id) - .then(() => { - setShowUserBlockModal(false); - showSuccessToast(t("user_blocked_successfully")); - navigate(-1); - }) - .catch(() => { - showErrorToast(t("try_again")); - }); - }; - - const handleCancelFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "CANCEL") - .then(updateUserProfile) - .catch(() => { - showErrorToast(t("try_again")); - }); - }; - - const handleAcceptFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "ACCEPTED") - .then(updateUserProfile) - .catch(() => { - showErrorToast(t("try_again")); - }); - }; - - const handleRefuseFriendRequest = (userId: string) => { - updateFriendRequestState(userId, "REFUSED") - .then(updateUserProfile) - .catch(() => { - showErrorToast(t("try_again")); - }); - }; + const showFriends = isMe || userProfile.totalFriends > 0; const getProfileActions = () => { if (isMe) { @@ -203,7 +178,10 @@ export function UserContent({ if (userProfile.relation == null) { return ( <> - @@ -215,12 +193,17 @@ export function UserContent({ } if (userProfile.relation.status === "ACCEPTED") { + const userId = + userProfile.relation.AId === userDetails?.id + ? userProfile.relation.BId + : userProfile.relation.AId; + return ( <> @@ -233,7 +216,9 @@ export function UserContent({ @@ -245,14 +230,18 @@ export function UserContent({ @@ -278,7 +267,7 @@ export function UserContent({ setShowUserBlockModal(false)} - onConfirm={handleBlockUser} + onConfirm={() => handleFriendAction(userProfile.id, "BLOCK")} displayName={userProfile.displayName} /> @@ -479,8 +468,7 @@ export function UserContent({ - {(isMe || - (userProfile.friends && userProfile.friends.totalFriends > 0)) && ( + {showFriends && (
@@ -512,7 +500,7 @@ export function UserContent({ gap: `${SPACING_UNIT}px`, }} > - {userProfile.friends.friends.map((friend) => { + {userProfile.friends.map((friend) => { return ( - ); + if (type === "ACCEPTED") { + return ( + + ); + } + + return null; }; return ( diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx index 2c639847..52e646e0 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-modal-list.tsx @@ -3,6 +3,8 @@ import { UserFriend } from "@types"; import { useEffect, useState } from "react"; import { UserFriendItem } from "./user-friend-item"; import { useNavigate } from "react-router-dom"; +import { useToast, useUserDetails } from "@renderer/hooks"; +import { useTranslation } from "react-i18next"; export interface UserFriendModalListProps { userId: string; @@ -15,12 +17,17 @@ export const UserFriendModalList = ({ userId, closeModal, }: UserFriendModalListProps) => { + const { t } = useTranslation("user_profile"); + const { showErrorToast } = useToast(); const navigate = useNavigate(); const [page, setPage] = useState(0); const [maxPage, setMaxPage] = useState(0); const [friends, setFriends] = useState([]); + const { userDetails, undoFriendship } = useUserDetails(); + const isMe = userDetails?.id == userId; + const loadNextPage = () => { if (page > maxPage) return; window.electron @@ -36,11 +43,15 @@ export const UserFriendModalList = ({ .catch(() => {}); }; - useEffect(() => { + const reloadList = () => { setPage(0); setMaxPage(0); setFriends([]); loadNextPage(); + }; + + useEffect(() => { + reloadList(); }, [userId]); const handleClickFriend = (userId: string) => { @@ -48,6 +59,16 @@ export const UserFriendModalList = ({ navigate(`/user/${userId}`); }; + const handleUndoFriendship = (userId: string) => { + undoFriendship(userId) + .then(() => { + reloadList(); + }) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + return (
{}} - onClickCancelRequest={() => {}} - onClickRefuseRequest={() => {}} onClickItem={handleClickFriend} - type={"ACCEPTED"} + onClickUndoFriendship={handleUndoFriendship} + type={isMe ? "ACCEPTED" : null} key={friend.id} /> ); diff --git a/src/types/index.ts b/src/types/index.ts index 0945d410..ac352a91 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -301,7 +301,7 @@ export interface UserProfile { id: string; displayName: string; profileImageUrl: string | null; - profileVisibility: "PUBLIC" | "PRIVATE" | "FRIEND"; + profileVisibility: "PUBLIC" | "PRIVATE" | "FRIENDS"; totalPlayTimeInSeconds: number; libraryGames: UserGame[]; recentGames: UserGame[];