diff --git a/electron.vite.config.1726264954825.mjs b/electron.vite.config.1726264954825.mjs deleted file mode 100644 index ed758ce8..00000000 --- a/electron.vite.config.1726264954825.mjs +++ /dev/null @@ -1,56 +0,0 @@ -// electron.vite.config.ts -import { resolve } from "path"; -import { - defineConfig, - loadEnv, - swcPlugin, - externalizeDepsPlugin, -} from "electron-vite"; -import react from "@vitejs/plugin-react"; -import { sentryVitePlugin } from "@sentry/vite-plugin"; -import { vanillaExtractPlugin } from "@vanilla-extract/vite-plugin"; -import svgr from "vite-plugin-svgr"; -var sentryPlugin = sentryVitePlugin({ - authToken: process.env.SENTRY_AUTH_TOKEN, - org: "hydra-launcher", - project: "hydra-launcher", -}); -var electron_vite_config_default = defineConfig(({ mode }) => { - loadEnv(mode); - return { - main: { - build: { - sourcemap: true, - rollupOptions: { - external: ["better-sqlite3"], - }, - }, - resolve: { - alias: { - "@main": resolve("src/main"), - "@locales": resolve("src/locales"), - "@resources": resolve("resources"), - "@shared": resolve("src/shared"), - }, - }, - plugins: [externalizeDepsPlugin(), swcPlugin(), sentryPlugin], - }, - preload: { - plugins: [externalizeDepsPlugin()], - }, - renderer: { - build: { - sourcemap: true, - }, - resolve: { - alias: { - "@renderer": resolve("src/renderer/src"), - "@locales": resolve("src/locales"), - "@shared": resolve("src/shared"), - }, - }, - plugins: [svgr(), react(), vanillaExtractPlugin(), sentryPlugin], - }, - }; -}); -export { electron_vite_config_default as default }; diff --git a/src/main/events/index.ts b/src/main/events/index.ts index e271a19b..0fe0d900 100644 --- a/src/main/events/index.ts +++ b/src/main/events/index.ts @@ -49,6 +49,8 @@ import "./user/get-blocked-users"; import "./user/block-user"; import "./user/unblock-user"; import "./user/get-user-friends"; +import "./user/get-user-stats"; +import "./user/report-user"; import "./profile/get-friend-requests"; import "./profile/get-me"; import "./profile/undo-friendship"; diff --git a/src/main/events/user/get-user-stats.ts b/src/main/events/user/get-user-stats.ts new file mode 100644 index 00000000..f88a4f12 --- /dev/null +++ b/src/main/events/user/get-user-stats.ts @@ -0,0 +1,12 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; +import type { UserStats } from "@types"; + +export const getUserStats = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string +): Promise => { + return HydraApi.get(`/users/${userId}/stats`); +}; + +registerEvent("getUserStats", getUserStats); diff --git a/src/main/events/user/get-user.ts b/src/main/events/user/get-user.ts index b7585298..9899ff52 100644 --- a/src/main/events/user/get-user.ts +++ b/src/main/events/user/get-user.ts @@ -27,12 +27,7 @@ const getUser = async ( userId: string ): Promise => { try { - const [profile, friends] = await Promise.all([ - HydraApi.get(`/users/${userId}`), - getUserFriends(userId, 12, 0).catch(() => { - return { totalFriends: 0, friends: [] }; - }), - ]); + const profile = await HydraApi.get(`/users/${userId}`); if (!profile) return null; @@ -77,10 +72,9 @@ const getUser = async ( ...profile, libraryGames, recentGames, - friends: friends.friends, - totalFriends: friends.totalFriends, }; } catch (err) { + console.log(err); return null; } }; diff --git a/src/main/events/user/report-user.ts b/src/main/events/user/report-user.ts new file mode 100644 index 00000000..1e8efbaa --- /dev/null +++ b/src/main/events/user/report-user.ts @@ -0,0 +1,16 @@ +import { registerEvent } from "../register-event"; +import { HydraApi } from "@main/services"; + +export const reportUser = async ( + _event: Electron.IpcMainInvokeEvent, + userId: string, + reason: string, + description: string +): Promise => { + return HydraApi.post(`/users/${userId}/report`, { + reason, + description, + }); +}; + +registerEvent("reportUser", reportUser); diff --git a/src/preload/index.ts b/src/preload/index.ts index 51498d4f..bba72cb9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -161,6 +161,9 @@ contextBridge.exposeInMainWorld("electron", { ipcRenderer.invoke("getUserFriends", userId, take, skip), getBlockedUsers: (take: number, skip: number) => ipcRenderer.invoke("getBlockedUsers", take, skip), + getUserStats: (userId: string) => ipcRenderer.invoke("getUserStats", userId), + reportUser: (userId: string, reason: string, description: string) => + ipcRenderer.invoke("reportUser", userId, reason, description), /* Auth */ signOut: () => ipcRenderer.invoke("signOut"), diff --git a/src/renderer/src/context/settings/settings.context.tsx b/src/renderer/src/context/settings/settings.context.tsx index dff006fc..f92f41f1 100644 --- a/src/renderer/src/context/settings/settings.context.tsx +++ b/src/renderer/src/context/settings/settings.context.tsx @@ -1,8 +1,8 @@ -import { createContext, useEffect, useState } from "react"; +import { createContext, useCallback, useEffect, useState } from "react"; import { setUserPreferences } from "@renderer/features"; import { useAppDispatch } from "@renderer/hooks"; -import type { UserPreferences } from "@types"; +import type { UserBlocks, UserPreferences } from "@types"; import { useSearchParams } from "react-router-dom"; export interface SettingsContext { @@ -11,6 +11,8 @@ export interface SettingsContext { clearSourceUrl: () => void; sourceUrl: string | null; currentCategoryIndex: number; + blockedUsers: UserBlocks["blocks"]; + fetchBlockedUsers: () => Promise; } export const settingsContext = createContext({ @@ -19,6 +21,8 @@ export const settingsContext = createContext({ clearSourceUrl: () => {}, sourceUrl: null, currentCategoryIndex: 0, + blockedUsers: [], + fetchBlockedUsers: async () => {}, }); const { Provider } = settingsContext; @@ -35,6 +39,8 @@ export function SettingsContextProvider({ const [sourceUrl, setSourceUrl] = useState(null); const [currentCategoryIndex, setCurrentCategoryIndex] = useState(0); + const [blockedUsers, setBlockedUsers] = useState([]); + const [searchParams] = useSearchParams(); const defaultSourceUrl = searchParams.get("urls"); @@ -48,6 +54,15 @@ export function SettingsContextProvider({ } }, [defaultSourceUrl]); + const fetchBlockedUsers = useCallback(async () => { + const blockedUsers = await window.electron.getBlockedUsers(12, 0); + setBlockedUsers(blockedUsers.blocks); + }, []); + + useEffect(() => { + fetchBlockedUsers(); + }, [fetchBlockedUsers]); + const clearSourceUrl = () => setSourceUrl(null); const updateUserPreferences = async (values: Partial) => { @@ -63,8 +78,10 @@ export function SettingsContextProvider({ updateUserPreferences, setCurrentCategoryIndex, clearSourceUrl, + fetchBlockedUsers, currentCategoryIndex, sourceUrl, + blockedUsers, }} > {children} diff --git a/src/renderer/src/context/user-profile/user-profile.context.tsx b/src/renderer/src/context/user-profile/user-profile.context.tsx index c3a2e7c2..1eb0c47c 100644 --- a/src/renderer/src/context/user-profile/user-profile.context.tsx +++ b/src/renderer/src/context/user-profile/user-profile.context.tsx @@ -1,6 +1,6 @@ import { darkenColor } from "@renderer/helpers"; import { useAppSelector, useToast } from "@renderer/hooks"; -import type { UserProfile } from "@types"; +import type { UserProfile, UserStats } from "@types"; import { average } from "color.js"; import { createContext, useCallback, useEffect, useState } from "react"; @@ -12,6 +12,7 @@ export interface UserProfileContext { heroBackground: string; /* Indicates if the current user is viewing their own profile */ isMe: boolean; + userStats: UserStats | null; getUserProfile: () => Promise; } @@ -22,6 +23,7 @@ export const userProfileContext = createContext({ userProfile: null, heroBackground: DEFAULT_USER_PROFILE_BACKGROUND, isMe: false, + userStats: null, getUserProfile: async () => {}, }); @@ -39,6 +41,8 @@ export function UserProfileContextProvider({ }: UserProfileContextProviderProps) { const { userDetails } = useAppSelector((state) => state.userDetails); + const [userStats, setUserStats] = useState(null); + const [userProfile, setUserProfile] = useState(null); const [heroBackground, setHeroBackground] = useState( DEFAULT_USER_PROFILE_BACKGROUND @@ -58,7 +62,15 @@ export function UserProfileContextProvider({ const { showErrorToast } = useToast(); const navigate = useNavigate(); + const getUserStats = useCallback(async () => { + window.electron.getUserStats(userId).then((stats) => { + setUserStats(stats); + }); + }, [userId]); + const getUserProfile = useCallback(async () => { + getUserStats(); + return window.electron.getUser(userId).then((userProfile) => { if (userProfile) { setUserProfile(userProfile); @@ -73,7 +85,7 @@ export function UserProfileContextProvider({ navigate(-1); } }); - }, [navigate, showErrorToast, userId, t]); + }, [navigate, getUserStats, showErrorToast, userId, t]); useEffect(() => { setUserProfile(null); @@ -89,6 +101,7 @@ export function UserProfileContextProvider({ heroBackground, isMe: userDetails?.id === userProfile?.id, getUserProfile, + userStats, }} > {children} diff --git a/src/renderer/src/declaration.d.ts b/src/renderer/src/declaration.d.ts index 70d6d463..f5828c2c 100644 --- a/src/renderer/src/declaration.d.ts +++ b/src/renderer/src/declaration.d.ts @@ -22,6 +22,7 @@ import type { UpdateProfileRequest, GameStats, TrendingGame, + UserStats, } from "@types"; import type { DiskSpace } from "check-disk-space"; @@ -143,6 +144,12 @@ declare global { skip: number ) => Promise; getBlockedUsers: (take: number, skip: number) => Promise; + getUserStats: (userId: string) => Promise; + reportUser: ( + userId: string, + reason: string, + description: string + ) => Promise; /* Profile */ getMe: () => Promise; diff --git a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts index 9e3bf3f3..783e4ffa 100644 --- a/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts +++ b/src/renderer/src/pages/game-details/sidebar/sidebar.css.ts @@ -97,7 +97,7 @@ export const statsSection = style({ }); export const statsCategoryTitle = style({ - fontSize: "16px", + fontSize: "14px", fontWeight: "bold", display: "flex", alignItems: "center", diff --git a/src/renderer/src/pages/profile/profile-content/friends-box.tsx b/src/renderer/src/pages/profile/profile-content/friends-box.tsx new file mode 100644 index 00000000..e2b4c9ba --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/friends-box.tsx @@ -0,0 +1,43 @@ +import { userProfileContext } from "@renderer/context"; +import { useFormat } from "@renderer/hooks"; +import { useContext } from "react"; +import { useTranslation } from "react-i18next"; + +import * as styles from "./profile-content.css"; +import { Link } from "@renderer/components"; + +export function FriendsBox() { + const { userProfile, userStats } = useContext(userProfileContext); + + const { t } = useTranslation("user_profile"); + + const { numberFormatter } = useFormat(); + + return ( +
+
+

{t("friends")}

+ {userStats && ( + {numberFormatter.format(userStats.friendsCount)} + )} +
+ +
+
    + {userProfile?.friends.map((friend) => ( +
  • + + {friend.displayName} + {friend.displayName} + +
  • + ))} +
+
+
+ ); +} diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts index 51026ebf..0c5aa04a 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.css.ts +++ b/src/renderer/src/pages/profile/profile-content/profile-content.css.ts @@ -150,3 +150,29 @@ export const noGames = style({ flexDirection: "column", gap: `${SPACING_UNIT}px`, }); + +export const listItemImage = style({ + width: "32px", + height: "32px", + borderRadius: "4px", +}); + +export const listItemDetails = style({ + display: "flex", + flexDirection: "column", + gap: `${SPACING_UNIT / 2}px`, + overflow: "hidden", +}); + +export const listItemTitle = style({ + fontWeight: "bold", + overflow: "hidden", + whiteSpace: "nowrap", + textOverflow: "ellipsis", +}); + +export const listItemDescription = style({ + display: "flex", + alignItems: "center", + gap: `${SPACING_UNIT}px`, +}); diff --git a/src/renderer/src/pages/profile/profile-content/profile-content.tsx b/src/renderer/src/pages/profile/profile-content/profile-content.tsx index a0db7404..4a9ad201 100644 --- a/src/renderer/src/pages/profile/profile-content/profile-content.tsx +++ b/src/renderer/src/pages/profile/profile-content/profile-content.tsx @@ -1,5 +1,5 @@ import { userProfileContext } from "@renderer/context"; -import { useCallback, useContext, useEffect, useMemo } from "react"; +import { useContext, useEffect, useMemo } from "react"; import { ProfileHero } from "../profile-hero/profile-hero"; import { useAppDispatch, useFormat } from "@renderer/hooks"; import { setHeaderTitle } from "@renderer/features"; @@ -7,23 +7,26 @@ import { steamUrlBuilder } from "@shared"; import { SPACING_UNIT } from "@renderer/theme.css"; import * as styles from "./profile-content.css"; -import { ClockIcon, TelescopeIcon } from "@primer/octicons-react"; -import { Link } from "@renderer/components"; +import { TelescopeIcon } from "@primer/octicons-react"; import { useTranslation } from "react-i18next"; -import { UserGame } from "@types"; -import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; -import { buildGameDetailsPath } from "@renderer/helpers"; import { useNavigate } from "react-router-dom"; import { LockedProfile } from "./locked-profile"; +import { ReportProfile } from "../report-profile/report-profile"; +import { FriendsBox } from "./friends-box"; +import { RecentGamesBox } from "./recent-games-box"; +import { UserGame } from "@types"; +import { buildGameDetailsPath } from "@renderer/helpers"; export function ProfileContent() { - const { userProfile, isMe } = useContext(userProfileContext); + const { userProfile, isMe, userStats } = useContext(userProfileContext); const dispatch = useAppDispatch(); const { t } = useTranslation("user_profile"); useEffect(() => { + dispatch(setHeaderTitle("")); + if (userProfile) { dispatch(setHeaderTitle(userProfile.displayName)); } @@ -33,22 +36,9 @@ export function ProfileContent() { const navigate = useNavigate(); - const formatPlayTime = useCallback( - (game: UserGame) => { - const seconds = game?.playTimeInSeconds || 0; - const minutes = seconds / 60; - - if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) { - return t("amount_minutes", { - amount: minutes.toFixed(0), - }); - } - - const hours = minutes / 60; - return t("amount_hours", { amount: numberFormatter.format(hours) }); - }, - [numberFormatter, t] - ); + const usersAreFriends = useMemo(() => { + return userProfile?.relation?.status === "ACCEPTED"; + }, [userProfile]); const buildUserGameDetailsPath = (game: UserGame) => buildGameDetailsPath({ @@ -56,10 +46,6 @@ export function ProfileContent() { objectID: game.objectId, }); - const usersAreFriends = useMemo(() => { - return userProfile?.relation?.status === "ACCEPTED"; - }, [userProfile]); - const content = useMemo(() => { if (!userProfile) return null; @@ -95,9 +81,9 @@ export function ProfileContent() {

{t("library")}

- - {numberFormatter.format(userProfile.libraryGames.length)} - + {userStats && ( + {numberFormatter.format(userStats.libraryCount)} + )}
    @@ -135,112 +121,14 @@ export function ProfileContent() {
    - {userProfile?.recentGames?.length > 0 && ( -
    -
    -

    {t("activity")}

    -
    + + -
    -
      - {userProfile?.recentGames.map((game) => ( -
    • - - {game.title} - -
      - - {game.title} - - -
      - - {formatPlayTime(game)} -
      -
      - -
    • - ))} -
    -
    -
    - )} - -
    -
    -

    {t("friends")}

    - {numberFormatter.format(userProfile?.totalFriends)} -
    - -
    -
      - {userProfile?.friends.map((friend) => ( -
    • - - {friend.displayName} - - {friend.displayName} - - -
    • - ))} -
    -
    -
    +
    ); - }, [ - userProfile, - formatPlayTime, - numberFormatter, - t, - usersAreFriends, - isMe, - navigate, - ]); + }, [userProfile, isMe, usersAreFriends, numberFormatter, t, navigate]); return (
    diff --git a/src/renderer/src/pages/profile/profile-content/recent-games-box.tsx b/src/renderer/src/pages/profile/profile-content/recent-games-box.tsx new file mode 100644 index 00000000..f6d3bc0d --- /dev/null +++ b/src/renderer/src/pages/profile/profile-content/recent-games-box.tsx @@ -0,0 +1,80 @@ +import { buildGameDetailsPath } from "@renderer/helpers"; + +import * as styles from "./profile-content.css"; +import { Link } from "@renderer/components"; +import { useCallback, useContext } from "react"; +import { userProfileContext } from "@renderer/context"; +import { useTranslation } from "react-i18next"; +import { ClockIcon } from "@primer/octicons-react"; +import { useFormat } from "@renderer/hooks"; +import type { UserGame } from "@types"; +import { MAX_MINUTES_TO_SHOW_IN_PLAYTIME } from "@renderer/constants"; + +export function RecentGamesBox() { + const { userProfile } = useContext(userProfileContext); + + const { t } = useTranslation("user_profile"); + + const { numberFormatter } = useFormat(); + + const formatPlayTime = useCallback( + (game: UserGame) => { + const seconds = game?.playTimeInSeconds || 0; + const minutes = seconds / 60; + + if (minutes < MAX_MINUTES_TO_SHOW_IN_PLAYTIME) { + return t("amount_minutes", { + amount: minutes.toFixed(0), + }); + } + + const hours = minutes / 60; + return t("amount_hours", { amount: numberFormatter.format(hours) }); + }, + [numberFormatter, t] + ); + + const buildUserGameDetailsPath = (game: UserGame) => + buildGameDetailsPath({ + ...game, + objectID: game.objectId, + }); + + if (!userProfile?.recentGames.length) return null; + + return ( +
    +
    +

    {t("activity")}

    +
    + +
    +
      + {userProfile?.recentGames.map((game) => ( +
    • + + {game.title} + +
      + {game.title} + +
      + + {formatPlayTime(game)} +
      +
      + +
    • + ))} +
    +
    +
    + ); +} diff --git a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx index 89c49852..9234f487 100644 --- a/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx +++ b/src/renderer/src/pages/profile/profile-hero/profile-hero.tsx @@ -167,14 +167,26 @@ export function ProfileHero() { if (userProfile.relation.status === "ACCEPTED") { return ( - + <> + + + ); } diff --git a/src/renderer/src/pages/profile/report-profile/report-profile.css.ts b/src/renderer/src/pages/profile/report-profile/report-profile.css.ts new file mode 100644 index 00000000..ba53fd62 --- /dev/null +++ b/src/renderer/src/pages/profile/report-profile/report-profile.css.ts @@ -0,0 +1,12 @@ +import { SPACING_UNIT, vars } from "../../../theme.css"; +import { style } from "@vanilla-extract/css"; + +export const reportButton = style({ + alignSelf: "flex-end", + color: vars.color.muted, + gap: `${SPACING_UNIT}px`, + display: "flex", + cursor: "pointer", + alignItems: "center", + fontSize: "12px", +}); diff --git a/src/renderer/src/pages/profile/report-profile/report-profile.tsx b/src/renderer/src/pages/profile/report-profile/report-profile.tsx new file mode 100644 index 00000000..758595f8 --- /dev/null +++ b/src/renderer/src/pages/profile/report-profile/report-profile.tsx @@ -0,0 +1,131 @@ +import { ReportIcon } from "@primer/octicons-react"; + +import * as styles from "./report-profile.css"; +import { Button, Modal, SelectField, TextField } from "@renderer/components"; +import { useCallback, useContext, useEffect, useState } from "react"; +import { Controller, useForm } from "react-hook-form"; +import { useTranslation } from "react-i18next"; +import * as yup from "yup"; +import { SPACING_UNIT } from "@renderer/theme.css"; +import { userProfileContext } from "@renderer/context"; +import { yupResolver } from "@hookform/resolvers/yup"; +import { useToast } from "@renderer/hooks"; + +const reportReasons = ["hate", "sexual_content", "violence", "spam", "other"]; + +interface FormValues { + reason: string; + description: string; +} + +export function ReportProfile() { + const [showReportProfileModal, setShowReportProfileModal] = useState(false); + + const { userProfile, isMe } = useContext(userProfileContext); + + const { t } = useTranslation("user_profile"); + + const schema = yup.object().shape({ + reason: yup.string().required(t("required_field")), + description: yup.string().required(t("required_field")), + }); + + const { + register, + control, + formState: { isSubmitting, errors }, + reset, + handleSubmit, + } = useForm({ + resolver: yupResolver(schema), + defaultValues: { + reason: "hate", + description: "", + }, + }); + + const { showSuccessToast } = useToast(); + + useEffect(() => { + reset({ + reason: "hate", + description: "", + }); + }, [userProfile, reset]); + + const onSubmit = useCallback( + async (values: FormValues) => { + return window.electron + .reportUser(userProfile!.id, values.reason, values.description) + .then(() => { + showSuccessToast(t("profile_reported")); + setShowReportProfileModal(false); + }); + }, + [userProfile, showSuccessToast, t] + ); + + if (isMe) return null; + + return ( + <> + setShowReportProfileModal(false)} + title="Report this profile" + clickOutsideToClose={false} + > +
    + { + return ( + ({ + key: reason, + value: reason, + label: t(`report_reason_${reason}`), + }))} + disabled={isSubmitting} + /> + ); + }} + /> + + + + + +
    + + + + ); +} diff --git a/src/renderer/src/pages/settings/settings-privacy.css.ts b/src/renderer/src/pages/settings/settings-privacy.css.ts index a0638e68..2aec8cd0 100644 --- a/src/renderer/src/pages/settings/settings-privacy.css.ts +++ b/src/renderer/src/pages/settings/settings-privacy.css.ts @@ -12,6 +12,7 @@ export const blockedUserAvatar = style({ width: "32px", height: "32px", borderRadius: "4px", + filter: "grayscale(100%)", }); export const blockedUser = style({ @@ -28,4 +29,19 @@ export const blockedUser = style({ export const unblockButton = style({ color: vars.color.muted, cursor: "pointer", + transition: "all ease 0.2s", + ":hover": { + opacity: "0.7", + }, +}); + +export const blockedUsersList = style({ + padding: "0", + margin: "0", + listStyle: "none", + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + gap: `${SPACING_UNIT}px`, + marginTop: `${SPACING_UNIT}px`, }); diff --git a/src/renderer/src/pages/settings/settings-privacy.tsx b/src/renderer/src/pages/settings/settings-privacy.tsx index 8ddebc16..a7fbe0f9 100644 --- a/src/renderer/src/pages/settings/settings-privacy.tsx +++ b/src/renderer/src/pages/settings/settings-privacy.tsx @@ -5,8 +5,9 @@ import { useTranslation } from "react-i18next"; import * as styles from "./settings-privacy.css"; import { useToast, useUserDetails } from "@renderer/hooks"; -import { useEffect, useState } from "react"; +import { useCallback, useContext, useEffect, useState } from "react"; import { XCircleFillIcon } from "@primer/octicons-react"; +import { settingsContext } from "@renderer/context"; interface FormValues { profileVisibility: "PUBLIC" | "FRIENDS" | "PRIVATE"; @@ -15,8 +16,12 @@ interface FormValues { export function SettingsPrivacy() { const { t } = useTranslation("settings"); + const [isUnblocking, setIsUnblocking] = useState(false); + const { showSuccessToast } = useToast(); + const { blockedUsers, fetchBlockedUsers } = useContext(settingsContext); + const { control, formState: { isSubmitting }, @@ -26,7 +31,7 @@ export function SettingsPrivacy() { const { patchUser, userDetails } = useUserDetails(); - const [blockedUsers, setBlockedUsers] = useState([]); + const { unblockUser } = useUserDetails(); useEffect(() => { if (userDetails?.profileVisibility) { @@ -34,14 +39,6 @@ export function SettingsPrivacy() { } }, [userDetails, setValue]); - useEffect(() => { - window.electron.getBlockedUsers(12, 0).then((users) => { - setBlockedUsers(users.blocks); - }); - }, []); - - console.log("BLOCKED USERS", blockedUsers); - const visibilityOptions = [ { value: "PUBLIC", label: t("public") }, { value: "FRIENDS", label: t("friends_only") }, @@ -53,6 +50,25 @@ export function SettingsPrivacy() { showSuccessToast(t("changes_saved")); }; + const handleUnblockClick = useCallback( + (id: string) => { + setIsUnblocking(true); + + unblockUser(id) + .then(() => { + fetchBlockedUsers(); + // show toast + }) + .catch((err) => { + //show toast + }) + .finally(() => { + setIsUnblocking(false); + }); + }, + [unblockUser, fetchBlockedUsers] + ); + return (
    -
      +
        {blockedUsers.map((user) => { return (
      • @@ -116,7 +125,12 @@ export function SettingsPrivacy() { {user.displayName}
    - diff --git a/src/types/index.ts b/src/types/index.ts index 5421c079..34bfa8b6 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -235,5 +235,10 @@ export interface TrendingGame { logo: string | null; } +export interface UserStats { + libraryCount: number; + friendsCount: number; +} + export * from "./steam.types"; export * from "./real-debrid.types";