From 6806787ca0e2cb4d4eeec2a68930f29ccd06732b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Tue, 6 Aug 2024 23:17:12 -0300 Subject: [PATCH 01/11] feat: set profile visibility --- src/locales/en/translation.json | 7 +- src/locales/pt/translation.json | 7 +- src/main/events/index.ts | 6 +- src/main/events/profile/update-profile.ts | 31 ++-- src/main/helpers/index.ts | 3 + src/preload/index.ts | 5 +- src/renderer/src/declaration.d.ts | 5 +- src/renderer/src/hooks/use-user-details.ts | 16 +- src/renderer/src/pages/user/user-content.tsx | 15 +- .../src/pages/user/user-edit-modal.tsx | 147 ----------------- .../user-profile-settings-modal/index.tsx | 1 + .../user-block-list.tsx | 0 .../user-edit-profile.tsx | 150 ++++++++++++++++++ .../user-profile-settings-modal.tsx | 72 +++++++++ src/types/index.ts | 7 + 15 files changed, 277 insertions(+), 195 deletions(-) delete mode 100644 src/renderer/src/pages/user/user-edit-modal.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/index.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx create mode 100644 src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index b24509d38..631e50c10 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -261,6 +261,11 @@ "undo_friendship": "Undo friendship", "request_accepted": "Request accepted", "user_blocked_successfully": "User blocked successfully", - "user_block_modal_text": "This will block {{displayName}}" + "user_block_modal_text": "This will block {{displayName}}", + "settings": "Settings", + "public": "Public", + "private": "Private", + "friends_only": "Friends only", + "privacy": "Privacy" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index ef94c31fc..5b24b574b 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -261,6 +261,11 @@ "undo_friendship": "Desfazer amizade", "request_accepted": "Pedido de amizade aceito", "user_blocked_successfully": "Usuário bloqueado com sucesso", - "user_block_modal_text": "Bloquear {{displayName}}" + "user_block_modal_text": "Bloquear {{displayName}}", + "settings": "Configurações", + "privacy": "Privacidade", + "private": "Privado", + "friends_only": "Apenas amigos", + "public": "Público" } } diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 57daf51c6..9cdc58b51 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -52,11 +52,9 @@ import "./profile/undo-friendship"; import "./profile/update-friend-request"; import "./profile/update-profile"; import "./profile/send-friend-request"; +import { isPortableVersion } from "@main/helpers"; ipcMain.handle("ping", () => "pong"); ipcMain.handle("getVersion", () => app.getVersion()); -ipcMain.handle( - "isPortableVersion", - () => process.env.PORTABLE_EXECUTABLE_FILE != null -); +ipcMain.handle("isPortableVersion", () => isPortableVersion()); ipcMain.handle("getDefaultDownloadsPath", () => defaultDownloadsPath); diff --git a/src/main/events/profile/update-profile.ts b/src/main/events/profile/update-profile.ts index 8620eaa1c..50d2ab66e 100644 --- a/src/main/events/profile/update-profile.ts +++ b/src/main/events/profile/update-profile.ts @@ -4,33 +4,22 @@ import axios from "axios"; import fs from "node:fs"; import path from "node:path"; import { fileTypeFromFile } from "file-type"; -import { UserProfile } from "@types"; - -const patchUserProfile = async ( - displayName: string, - profileImageUrl?: string -) => { - if (profileImageUrl) { - return HydraApi.patch("/profile", { - displayName, - profileImageUrl, - }); - } else { - return HydraApi.patch("/profile", { - displayName, - }); - } +import { UpdateProfileProps, UserProfile } from "@types"; + +const patchUserProfile = async (updateProfile: UpdateProfileProps) => { + return HydraApi.patch("/profile", updateProfile); }; const updateProfile = async ( _event: Electron.IpcMainInvokeEvent, - displayName: string, - newProfileImagePath: string | null + updateProfile: UpdateProfileProps ): Promise => { - if (!newProfileImagePath) { - return patchUserProfile(displayName); + if (!updateProfile.profileImageUrl) { + return patchUserProfile(updateProfile); } + const newProfileImagePath = updateProfile.profileImageUrl; + const stats = fs.statSync(newProfileImagePath); const fileBuffer = fs.readFileSync(newProfileImagePath); const fileSizeInBytes = stats.size; @@ -53,7 +42,7 @@ const updateProfile = async ( }) .catch(() => undefined); - return patchUserProfile(displayName, profileImageUrl); + return patchUserProfile({ ...updateProfile, profileImageUrl }); }; registerEvent("updateProfile", updateProfile); diff --git a/src/main/helpers/index.ts b/src/main/helpers/index.ts index 902b927d1..f2b86e5a9 100644 --- a/src/main/helpers/index.ts +++ b/src/main/helpers/index.ts @@ -57,4 +57,7 @@ export const requestWebPage = async (url: string) => { .then((response) => response.data); }; +export const isPortableVersion = () => + process.env.PORTABLE_EXECUTABLE_FILE != null; + export * from "./download-source"; diff --git a/src/preload/index.ts b/src/preload/index.ts index 3350a3405..84100cab3 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -10,6 +10,7 @@ import type { StartGameDownloadPayload, GameRunning, FriendRequestAction, + UpdateProfileProps, } from "@types"; contextBridge.exposeInMainWorld("electron", { @@ -137,8 +138,8 @@ contextBridge.exposeInMainWorld("electron", { getMe: () => ipcRenderer.invoke("getMe"), undoFriendship: (userId: string) => ipcRenderer.invoke("undoFriendship", userId), - updateProfile: (displayName: string, newProfileImagePath: string | null) => - ipcRenderer.invoke("updateProfile", displayName, newProfileImagePath), + updateProfile: (updateProfile: UpdateProfileProps) => + ipcRenderer.invoke("updateProfile", updateProfile), getFriendRequests: () => ipcRenderer.invoke("getFriendRequests"), updateFriendRequest: (userId: string, action: FriendRequestAction) => ipcRenderer.invoke("updateFriendRequest", userId, action), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index e022cffed..3e954d405 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -139,10 +139,7 @@ declare global { /* Profile */ getMe: () => Promise; undoFriendship: (userId: string) => Promise; - updateProfile: ( - displayName: string, - newProfileImagePath: string | null - ) => Promise; + updateProfile: (updateProfile: UpdateProfileProps) => Promise; getFriendRequests: () => Promise; updateFriendRequest: ( userId: string, diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index 21690e7e6..e33de9786 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -8,8 +8,9 @@ import { setFriendsModalHidden, } from "@renderer/features"; import { profileBackgroundFromProfileImage } from "@renderer/helpers"; -import { FriendRequestAction, UserDetails } from "@types"; +import { FriendRequestAction, UpdateProfileProps, UserDetails } from "@types"; import { UserFriendModalTab } from "@renderer/pages/shared-modals/user-friend-modal"; +import { logger } from "@renderer/logger"; export function useUserDetails() { const dispatch = useAppDispatch(); @@ -43,7 +44,10 @@ export function useUserDetails() { if (userDetails.profileImageUrl) { const profileBackground = await profileBackgroundFromProfileImage( userDetails.profileImageUrl - ); + ).catch((err) => { + logger.error("profileBackgroundFromProfileImage", err); + return `#151515B3`; + }); dispatch(setProfileBackground(profileBackground)); window.localStorage.setItem( @@ -74,12 +78,8 @@ export function useUserDetails() { }, [clearUserDetails]); const patchUser = useCallback( - async (displayName: string, imageProfileUrl: string | null) => { - const response = await window.electron.updateProfile( - displayName, - imageProfileUrl - ); - + async (props: UpdateProfileProps) => { + const response = await window.electron.updateProfile(props); return updateUserDetails(response); }, [updateUserDetails] diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 81db119d6..7367ed5fc 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -25,7 +25,7 @@ import { XCircleIcon, } from "@primer/octicons-react"; import { Button, Link } from "@renderer/components"; -import { UserEditProfileModal } from "./user-edit-modal"; +import { UserProfileSettingsModal } from "./user-profile-settings-modal"; import { UserSignOutModal } from "./user-sign-out-modal"; import { UserFriendModalTab } from "../shared-modals/user-friend-modal"; import { UserBlockModal } from "./user-block-modal"; @@ -60,7 +60,8 @@ export function UserContent({ const [profileContentBoxBackground, setProfileContentBoxBackground] = useState(); - const [showEditProfileModal, setShowEditProfileModal] = useState(false); + const [showProfileSettingsModal, setShowProfileSettingsModal] = + useState(false); const [showSignOutModal, setShowSignOutModal] = useState(false); const [showUserBlockModal, setShowUserBlockModal] = useState(false); @@ -95,7 +96,7 @@ export function UserContent({ }; const handleEditProfile = () => { - setShowEditProfileModal(true); + setShowProfileSettingsModal(true); }; const handleOnClickFriend = (userId: string) => { @@ -165,7 +166,7 @@ export function UserContent({ return ( <> - - setDisplayName(e.target.value)} - /> - - - - - ); -}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx new file mode 100644 index 000000000..896d36848 --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/index.tsx @@ -0,0 +1 @@ +export * from "./user-profile-settings-modal"; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx new file mode 100644 index 000000000..aa78c1ea5 --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-edit-profile.tsx @@ -0,0 +1,150 @@ +import { DeviceCameraIcon, PersonIcon } from "@primer/octicons-react"; +import { Button, SelectField, TextField } from "@renderer/components"; +import { useToast, useUserDetails } from "@renderer/hooks"; +import { UserProfile } from "@types"; +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import * as styles from "../user.css"; +import { SPACING_UNIT } from "@renderer/theme.css"; + +export interface UserEditProfileProps { + userProfile: UserProfile; + updateUserProfile: () => Promise; +} + +export const UserEditProfile = ({ + userProfile, + updateUserProfile, +}: UserEditProfileProps) => { + const { t } = useTranslation("user_profile"); + + const [form, setForm] = useState({ + displayName: userProfile.displayName, + profileVisibility: userProfile.profileVisibility, + imageProfileUrl: null as string | null, + }); + const [isSaving, setIsSaving] = useState(false); + + const { patchUser } = useUserDetails(); + + const { showSuccessToast, showErrorToast } = useToast(); + + const [profileVisibilityOptions, setProfileVisibilityOptions] = useState< + { value: string; label: string }[] + >([]); + + useEffect(() => { + setProfileVisibilityOptions([ + { value: "PUBLIC", label: t("public") }, + { value: "FRIENDS", label: t("friends_only") }, + { value: "PRIVATE", label: t("private") }, + ]); + }, [t]); + + const handleChangeProfileAvatar = async () => { + const { filePaths } = await window.electron.showOpenDialog({ + properties: ["openFile"], + filters: [ + { + name: "Image", + extensions: ["jpg", "jpeg", "png", "webp"], + }, + ], + }); + + if (filePaths && filePaths.length > 0) { + const path = filePaths[0]; + + setForm({ ...form, imageProfileUrl: path }); + } + }; + + const handleProfileVisibilityChange = (event) => { + setForm({ + ...form, + profileVisibility: event.target.value, + }); + }; + + const handleSaveProfile: React.FormEventHandler = async ( + event + ) => { + event.preventDefault(); + setIsSaving(true); + + patchUser(form) + .then(async () => { + await updateUserProfile(); + showSuccessToast(t("saved_successfully")); + }) + .catch(() => { + showErrorToast(t("try_again")); + }) + .finally(() => { + setIsSaving(false); + }); + }; + + const avatarUrl = useMemo(() => { + if (form.imageProfileUrl) return `local:${form.imageProfileUrl}`; + if (userProfile.profileImageUrl) return userProfile.profileImageUrl; + return null; + }, [form, userProfile]); + + return ( +
+ + + setForm({ ...form, displayName: e.target.value })} + /> + + ({ + key: visiblity.value, + value: visiblity.value, + label: visiblity.label, + }))} + /> + + + + ); +}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx new file mode 100644 index 000000000..5615cf12c --- /dev/null +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -0,0 +1,72 @@ +import { Button, Modal } from "@renderer/components"; +import { UserProfile } from "@types"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { UserEditProfile } from "./user-edit-profile"; + +export interface UserEditProfileModalProps { + userProfile: UserProfile; + visible: boolean; + onClose: () => void; + updateUserProfile: () => Promise; +} + +export const UserProfileSettingsModal = ({ + userProfile, + visible, + onClose, + updateUserProfile, +}: UserEditProfileModalProps) => { + const { t } = useTranslation("user_profile"); + + const tabs = [t("edit_profile"), "Ban list"]; + + const [currentTabIndex, setCurrentTabIndex] = useState(0); + + const renderTab = () => { + if (currentTabIndex == 0) { + return ( + + ); + } + + if (currentTabIndex == 1) { + return <>; + } + + return <>; + }; + + return ( + <> + +
+
+ {tabs.map((tab, index) => { + return ( + + ); + })} +
+ {renderTab()} +
+
+ + ); +}; diff --git a/src/types/index.ts b/src/types/index.ts index ac352a918..cb2f8c277 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -310,6 +310,13 @@ export interface UserProfile { relation: UserRelation | null; } +export interface UpdateProfileProps { + displayName?: string; + profileVisibility?: "PUBLIC" | "PRIVATE" | "FRIENDS"; + profileImageUrl?: string | null; + bio?: string; +} + export interface DownloadSource { id: number; name: string; From 7e6b9ca825684fec61aa56b17fcf183ea85104d4 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:12:26 -0300 Subject: [PATCH 02/11] feat: conditional to show user content section based on visibility --- src/locales/en/translation.json | 3 +- src/locales/pt/translation.json | 3 +- src/renderer/src/pages/user/user-content.tsx | 326 +++++++++--------- .../user-profile-settings-modal.tsx | 2 +- 4 files changed, 174 insertions(+), 160 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 631e50c10..9e190778a 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -266,6 +266,7 @@ "public": "Public", "private": "Private", "friends_only": "Friends only", - "privacy": "Privacy" + "privacy": "Privacy", + "blocked_users": "Blocked users" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 5b24b574b..036e2ff0d 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -266,6 +266,7 @@ "privacy": "Privacidade", "private": "Privado", "friends_only": "Apenas amigos", - "public": "Público" + "public": "Público", + "blocked_users": "Usuários bloqueados" } } diff --git a/src/renderer/src/pages/user/user-content.tsx b/src/renderer/src/pages/user/user-content.tsx index 7367ed5fc..b9502231a 100644 --- a/src/renderer/src/pages/user/user-content.tsx +++ b/src/renderer/src/pages/user/user-content.tsx @@ -160,6 +160,11 @@ export function UserContent({ }; const showFriends = isMe || userProfile.totalFriends > 0; + const showProfileContent = + isMe || + userProfile.profileVisibility === "PUBLIC" || + (userProfile.relation?.status === "ACCEPTED" && + userProfile.profileVisibility === "FRIENDS"); const getProfileActions = () => { if (isMe) { @@ -362,125 +367,73 @@ export function UserContent({ -
-
-

{t("activity")}

- - {!userProfile.recentGames.length ? ( -
-
- -
-

{t("no_recent_activity_title")}

- {isMe && ( -

- {t("no_recent_activity_description")} -

- )} -
- ) : ( -
- {userProfile.recentGames.map((game) => ( - - ))} -
- )} -
- -
+ {showProfileContent && ( +
-
-

{t("library")}

- +

{t("activity")}

+ + {!userProfile.recentGames.length ? ( +
+
+ +
+

{t("no_recent_activity_title")}

+ {isMe && ( +

+ {t("no_recent_activity_description")} +

+ )} +
+ ) : (
-

- {userProfile.libraryGames.length} -

-
- {t("total_play_time", { amount: formatPlayTime() })} -
- {userProfile.libraryGames.map((game) => ( - - ))} -
+
+

{game.title}

+ + {t("last_time_played", { + period: formatDistance( + game.lastTimePlayed!, + new Date(), + { + addSuffix: true, + } + ), + })} + +
+ + ))} +
+ )}
- {showFriends && ( -
- - +
+ + {t("total_play_time", { amount: formatPlayTime() })} +
- {userProfile.friends.map((friend) => { - return ( - - ); - })} - - {isMe && ( - - )} + {game.iconUrl ? ( + {game.title} + ) : ( + + )} + + ))}
- )} + + {showFriends && ( +
+ + +
+ {userProfile.friends.map((friend) => { + return ( + + ); + })} + + {isMe && ( + + )} +
+
+ )} +
- + )} ); } diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx index 5615cf12c..7818b4f8f 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -20,7 +20,7 @@ export const UserProfileSettingsModal = ({ }: UserEditProfileModalProps) => { const { t } = useTranslation("user_profile"); - const tabs = [t("edit_profile"), "Ban list"]; + const tabs = [t("edit_profile"), t("blocked_users")]; const [currentTabIndex, setCurrentTabIndex] = useState(0); From fbe3c1973a7d9a2b6995d1d1b3f49d5b4a0ad85b Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:45:48 -0300 Subject: [PATCH 03/11] feat: list blocked users --- src/main/events/index.ts | 1 + src/main/events/user/get-user-blocks.ts | 13 +++ src/preload/index.ts | 2 + src/renderer/src/declaration.d.ts | 2 + .../user-block-list.tsx | 92 +++++++++++++++++++ .../user-profile-settings-modal.tsx | 7 +- src/types/index.ts | 5 + 7 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/main/events/user/get-user-blocks.ts diff --git a/src/main/events/index.ts b/src/main/events/index.ts index 9cdc58b51..3963e4b05 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-blocks"; import "./user/block-user"; import "./user/unblock-user"; import "./user/get-user-friends"; diff --git a/src/main/events/user/get-user-blocks.ts b/src/main/events/user/get-user-blocks.ts new file mode 100644 index 000000000..65bb3eb48 --- /dev/null +++ b/src/main/events/user/get-user-blocks.ts @@ -0,0 +1,13 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import { UserBlocks } from "@types"; + +export const getUserBlocks = async ( + _event: Electron.IpcMainInvokeEvent, + take: number, + skip: number +): Promise => { + return HydraApi.get(`/profile/blocks`, { take, skip }); +}; + +registerEvent("getUserBlocks", getUserBlocks); diff --git a/src/preload/index.ts b/src/preload/index.ts index 84100cab3..087d573a0 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -152,6 +152,8 @@ contextBridge.exposeInMainWorld("electron", { unblockUser: (userId: string) => ipcRenderer.invoke("unblockUser", userId), getUserFriends: (userId: string, take: number, skip: number) => ipcRenderer.invoke("getUserFriends", userId, take, skip), + getUserBlocks: (take: number, skip: number) => + ipcRenderer.invoke("getUserBlocks", take, skip), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 3e954d405..29e4dcbb6 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -17,6 +17,7 @@ import type { FriendRequest, FriendRequestAction, UserFriends, + UserBlocks, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -135,6 +136,7 @@ declare global { take: number, skip: number ) => Promise; + getUserBlocks: (take: number, skip: number) => Promise; /* Profile */ getMe: () => Promise; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx index e69de29bb..5b5d0e74a 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-block-list.tsx @@ -0,0 +1,92 @@ +import { SPACING_UNIT } from "@renderer/theme.css"; +import { UserFriend } from "@types"; +import { useEffect, useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useToast, useUserDetails } from "@renderer/hooks"; +import { useTranslation } from "react-i18next"; +import { UserFriendItem } from "@renderer/pages/shared-modals/user-friend-modal/user-friend-item"; + +export interface UserEditProfileBlockListProps { + closeModal: () => void; +} + +const pageSize = 12; + +export const UserEditProfileBlockList = ({ + closeModal, +}: UserEditProfileBlockListProps) => { + const { t } = useTranslation("user_profile"); + const { showErrorToast } = useToast(); + const navigate = useNavigate(); + + const [page, setPage] = useState(0); + const [maxPage, setMaxPage] = useState(0); + const [blocks, setBlocks] = useState([]); + + const { unblockUser } = useUserDetails(); + + const loadNextPage = () => { + if (page > maxPage) return; + window.electron + .getUserBlocks(pageSize, page * pageSize) + .then((newPage) => { + if (page === 0) { + setMaxPage(newPage.totalBlocks / pageSize); + } + + setBlocks([...blocks, ...newPage.blocks]); + setPage(page + 1); + }) + .catch(() => {}); + }; + + const reloadList = () => { + setPage(0); + setMaxPage(0); + setBlocks([]); + loadNextPage(); + }; + + useEffect(() => { + reloadList(); + }, []); + + const handleClickBlocked = (userId: string) => { + closeModal(); + navigate(`/user/${userId}`); + }; + + const handleUnblock = (userId: string) => { + unblockUser(userId) + .then(() => { + reloadList(); + }) + .catch(() => { + showErrorToast(t("try_again")); + }); + }; + + return ( +
+ {blocks.map((friend) => { + return ( + + ); + })} +
+ ); +}; diff --git a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx index 7818b4f8f..a2fcb3726 100644 --- a/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx +++ b/src/renderer/src/pages/user/user-profile-settings-modal/user-profile-settings-modal.tsx @@ -4,8 +4,9 @@ import { SPACING_UNIT } from "@renderer/theme.css"; import { useState } from "react"; import { useTranslation } from "react-i18next"; import { UserEditProfile } from "./user-edit-profile"; +import { UserEditProfileBlockList } from "./user-block-list"; -export interface UserEditProfileModalProps { +export interface UserProfileSettingsModalProps { userProfile: UserProfile; visible: boolean; onClose: () => void; @@ -17,7 +18,7 @@ export const UserProfileSettingsModal = ({ visible, onClose, updateUserProfile, -}: UserEditProfileModalProps) => { +}: UserProfileSettingsModalProps) => { const { t } = useTranslation("user_profile"); const tabs = [t("edit_profile"), t("blocked_users")]; @@ -35,7 +36,7 @@ export const UserProfileSettingsModal = ({ } if (currentTabIndex == 1) { - return <>; + return ; } return <>; diff --git a/src/types/index.ts b/src/types/index.ts index cb2f8c277..6200d8251 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -282,6 +282,11 @@ export interface UserFriends { friends: UserFriend[]; } +export interface UserBlocks { + totalBlocks: number; + blocks: UserFriend[]; +} + export interface FriendRequest { id: string; displayName: string; From 80123d67e1e6b9a9991b9dfc2912feeebc86d3b6 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Wed, 14 Aug 2024 22:22:35 -0300 Subject: [PATCH 04/11] feat: adjust ui --- src/locales/en/translation.json | 3 +- src/locales/pt/translation.json | 3 +- src/renderer/src/app.tsx | 10 ++- src/renderer/src/hooks/use-user-details.ts | 2 +- .../user-friend-modal/user-friend-item.tsx | 69 +++++++++++++++++-- .../user-block-list.tsx | 20 +----- .../user-profile-settings-modal.tsx | 2 +- 7 files changed, 81 insertions(+), 28 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 9e190778a..5dce5893d 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -267,6 +267,7 @@ "private": "Private", "friends_only": "Friends only", "privacy": "Privacy", - "blocked_users": "Blocked users" + "blocked_users": "Blocked users", + "unblock": "Unblock" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 036e2ff0d..97c59b47a 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -267,6 +267,7 @@ "private": "Privado", "friends_only": "Apenas amigos", "public": "Público", - "blocked_users": "Usuários bloqueados" + "blocked_users": "Usuários bloqueados", + "unblock": "Desbloquear" } } diff --git a/src/renderer/src/app.tsx b/src/renderer/src/app.tsx index 8c6f7604e..2b9ac1874 100644 --- a/src/renderer/src/app.tsx +++ b/src/renderer/src/app.tsx @@ -108,7 +108,7 @@ export function App() { fetchFriendRequests(); } }); - }, [fetchUserDetails, updateUserDetails, dispatch]); + }, [fetchUserDetails, fetchFriendRequests, updateUserDetails, dispatch]); const onSignIn = useCallback(() => { fetchUserDetails().then((response) => { @@ -118,7 +118,13 @@ export function App() { showSuccessToast(t("successfully_signed_in")); } }); - }, [fetchUserDetails, t, showSuccessToast, updateUserDetails]); + }, [ + fetchUserDetails, + fetchFriendRequests, + t, + showSuccessToast, + updateUserDetails, + ]); useEffect(() => { const unsubscribe = window.electron.onGamesRunning((gamesRunning) => { diff --git a/src/renderer/src/hooks/use-user-details.ts b/src/renderer/src/hooks/use-user-details.ts index e33de9786..0cf2a381c 100644 --- a/src/renderer/src/hooks/use-user-details.ts +++ b/src/renderer/src/hooks/use-user-details.ts @@ -99,7 +99,7 @@ export function useUserDetails() { dispatch(setFriendsModalVisible({ initialTab, userId })); fetchFriendRequests(); }, - [dispatch] + [dispatch, fetchFriendRequests] ); const hideFriendsModal = useCallback(() => { diff --git a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-item.tsx b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-item.tsx index 7c34d0401..2c283ac12 100644 --- a/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-item.tsx +++ b/src/renderer/src/pages/shared-modals/user-friend-modal/user-friend-item.tsx @@ -12,21 +12,26 @@ export type UserFriendItemProps = { userId: string; profileImageUrl: string | null; displayName: string; - onClickItem: (userId: string) => void; } & ( - | { type: "ACCEPTED"; onClickUndoFriendship: (userId: string) => void } + | { + type: "ACCEPTED"; + onClickUndoFriendship: (userId: string) => void; + onClickItem: (userId: string) => void; + } + | { type: "BLOCKED"; onClickUnblock: (userId: string) => void } | { type: "SENT" | "RECEIVED"; onClickCancelRequest: (userId: string) => void; onClickAcceptRequest: (userId: string) => void; onClickRefuseRequest: (userId: string) => void; + onClickItem: (userId: string) => void; } - | { type: null } + | { type: null; onClickItem: (userId: string) => void } ); export const UserFriendItem = (props: UserFriendItemProps) => { const { t } = useTranslation("user_profile"); - const { userId, profileImageUrl, displayName, type, onClickItem } = props; + const { userId, profileImageUrl, displayName, type } = props; const getRequestDescription = () => { if (type === "ACCEPTED" || type === null) return null; @@ -86,15 +91,69 @@ export const UserFriendItem = (props: UserFriendItemProps) => { ); } + if (type === "BLOCKED") { + return ( + + ); + } + return null; }; + if (type === "BLOCKED") { + return ( +
+
+
+ {profileImageUrl ? ( + {displayName} + ) : ( + + )} +
+
+

{displayName}

+
+
+ +
+ {getRequestActions()} +
+
+ ); + } + return (
- ); - })} - + <> +
+

Meu código de amigo:

+ +
+
+ {tabs.map((tab, index) => { + return ( + + ); + })} +
+ )} {renderTab()}
From f3276dd8fe6637bb3f8ccc1a2fd35a1f91a792c1 Mon Sep 17 00:00:00 2001 From: Zamitto <167933696+zamitto@users.noreply.github.com> Date: Fri, 16 Aug 2024 13:21:12 -0300 Subject: [PATCH 07/11] feat: add texts for no invites, no friends and no blocks --- src/locales/en/translation.json | 6 +++++- src/locales/pt/translation.json | 6 +++++- .../user-friend-modal-add-friend.tsx | 17 +++++++---------- .../user-friend-modal-list.tsx | 1 + .../user-friend-modal/user-friend-modal.css.ts | 12 ++++++++++++ .../user-friend-modal/user-friend-modal.tsx | 13 ++++--------- .../user-block-list.tsx | 1 + 7 files changed, 35 insertions(+), 21 deletions(-) diff --git a/src/locales/en/translation.json b/src/locales/en/translation.json index 5dce5893d..db64ed3cc 100644 --- a/src/locales/en/translation.json +++ b/src/locales/en/translation.json @@ -268,6 +268,10 @@ "friends_only": "Friends only", "privacy": "Privacy", "blocked_users": "Blocked users", - "unblock": "Unblock" + "unblock": "Unblock", + "no_friends_added": "You still don't have added friends", + "pending": "Pending", + "no_pending_invites": "You have no pending invites", + "no_blocked_users": "You have no blocked users" } } diff --git a/src/locales/pt/translation.json b/src/locales/pt/translation.json index 97c59b47a..5cb6fd2fc 100644 --- a/src/locales/pt/translation.json +++ b/src/locales/pt/translation.json @@ -268,6 +268,10 @@ "friends_only": "Apenas amigos", "public": "Público", "blocked_users": "Usuários bloqueados", - "unblock": "Desbloquear" + "unblock": "Desbloquear", + "no_friends_added": "Você ainda não possui amigos adicionados", + "pending": "Pendentes", + "no_pending_invites": "Você não possui convites de amizade pendentes", + "no_blocked_users": "Você não tem nenhum usuário bloqueado" } } 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 0725674e7..b6e6aaea1 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 @@ -40,20 +40,16 @@ export const UserFriendModalAddFriend = ({ }); }; - const resetAndClose = () => { - setFriendCode(""); - closeModal(); - }; - const handleClickRequest = (userId: string) => { - resetAndClose(); + closeModal(); navigate(`/user/${userId}`); }; const handleClickSeeProfile = () => { - resetAndClose(); - // TODO: add validation for this input? - navigate(`/user/${friendCode}`); + closeModal(); + if (friendCode.length === 8) { + navigate(`/user/${friendCode}`); + } }; const handleCancelFriendRequest = (userId: string) => { @@ -122,7 +118,8 @@ export const UserFriendModalAddFriend = ({ gap: `${SPACING_UNIT * 2}px`, }} > -

Pendentes

+

{t("pending")}

+ {friendRequests.length === 0 &&

{t("no_pending_invites")}

} {friendRequests.map((request) => { return ( + {friends.length === 0 &&

{t("no_friends_added")}

} {friends.map((friend) => { return ( -

Meu código de amigo:

+

Seu código de amigo: